|
|
|
@ -15,18 +15,15 @@ |
|
|
|
* { |
|
|
|
* { |
|
|
|
box-sizing: border-box; |
|
|
|
box-sizing: border-box; |
|
|
|
} |
|
|
|
} |
|
|
|
.main-layout { |
|
|
|
.results-input-container { |
|
|
|
display: grid; |
|
|
|
|
|
|
|
grid-template-columns: calc(100vw - 50px); |
|
|
|
|
|
|
|
grid-template-rows: min-content 1fr; |
|
|
|
|
|
|
|
grid-template-areas: |
|
|
|
|
|
|
|
"top top" |
|
|
|
|
|
|
|
"bottom bottom"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.top-bar { |
|
|
|
|
|
|
|
grid-area: top; |
|
|
|
|
|
|
|
padding: 15px; |
|
|
|
padding: 15px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.results-input-container .title-bar { |
|
|
|
|
|
|
|
padding: 0 0 15px 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.results-input-container .title-bar h2 { |
|
|
|
|
|
|
|
margin: 0; |
|
|
|
|
|
|
|
} |
|
|
|
#results-input { |
|
|
|
#results-input { |
|
|
|
padding: 0.5em; |
|
|
|
padding: 0.5em; |
|
|
|
width: 100%; |
|
|
|
width: 100%; |
|
|
|
@ -35,9 +32,28 @@ |
|
|
|
/* Category refinement */ |
|
|
|
/* Category refinement */ |
|
|
|
.category-refinement { |
|
|
|
.category-refinement { |
|
|
|
grid-area: bottom; |
|
|
|
grid-area: bottom; |
|
|
|
|
|
|
|
position: relative; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.category-refinement .title-bar { |
|
|
|
|
|
|
|
position: sticky; |
|
|
|
|
|
|
|
display: flex; |
|
|
|
|
|
|
|
align-items: center; |
|
|
|
|
|
|
|
top: 0; |
|
|
|
|
|
|
|
gap: 15px; |
|
|
|
|
|
|
|
padding: 15px; |
|
|
|
|
|
|
|
background: #fff; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.category-refinement .title-bar h2 { |
|
|
|
|
|
|
|
margin: 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.category-refinement .title-bar button { |
|
|
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.category-refinement .title-bar button[disabled] { |
|
|
|
|
|
|
|
cursor: not-allowed; |
|
|
|
} |
|
|
|
} |
|
|
|
.category-refinement .categories { |
|
|
|
.category-refinement .categories { |
|
|
|
padding: 10px; |
|
|
|
padding: 15px; |
|
|
|
} |
|
|
|
} |
|
|
|
.category-refinement .category { |
|
|
|
.category-refinement .category { |
|
|
|
display: grid; |
|
|
|
display: grid; |
|
|
|
@ -83,6 +99,11 @@ |
|
|
|
.category-refinement .category-merge-button-start:focus { |
|
|
|
.category-refinement .category-merge-button-start:focus { |
|
|
|
opacity: 1; |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@media (hover: none) { |
|
|
|
|
|
|
|
.category-refinement .category-merge-button-start { |
|
|
|
|
|
|
|
opacity: 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
.category-refinement .categories:not(.is-merge-active) .category-merge-button-end, |
|
|
|
.category-refinement .categories:not(.is-merge-active) .category-merge-button-end, |
|
|
|
.category-refinement .categories.is-merge-active .is-merge-source .category-merge-button-end, |
|
|
|
.category-refinement .categories.is-merge-active .is-merge-source .category-merge-button-end, |
|
|
|
.category-refinement .categories.is-merge-active .category-merge-button-start, |
|
|
|
.category-refinement .categories.is-merge-active .category-merge-button-start, |
|
|
|
@ -101,7 +122,8 @@ |
|
|
|
.category-refinement .category.is-merge-pending { |
|
|
|
.category-refinement .category.is-merge-pending { |
|
|
|
opacity: .3; |
|
|
|
opacity: .3; |
|
|
|
} |
|
|
|
} |
|
|
|
.category-refinement .category.is-merge-pending .category-name { |
|
|
|
.category-refinement .category.is-merge-pending .category-name, |
|
|
|
|
|
|
|
.category-refinement .merged-category.is-undo-pending .merged-category-name { |
|
|
|
text-decoration: line-through; |
|
|
|
text-decoration: line-through; |
|
|
|
} |
|
|
|
} |
|
|
|
.category-refinement .merged-category { |
|
|
|
.category-refinement .merged-category { |
|
|
|
@ -148,7 +170,16 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
/* Association table */ |
|
|
|
/* Association table */ |
|
|
|
.associations { |
|
|
|
.associations { |
|
|
|
padding: 30px; |
|
|
|
padding: 15px; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.associations .title-bar { |
|
|
|
|
|
|
|
position: sticky; |
|
|
|
|
|
|
|
top: 0; |
|
|
|
|
|
|
|
padding: 15px 0; |
|
|
|
|
|
|
|
background: #fff; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
.associations .title-bar h2 { |
|
|
|
|
|
|
|
margin: 0; |
|
|
|
} |
|
|
|
} |
|
|
|
.association-table { |
|
|
|
.association-table { |
|
|
|
border-collapse: collapse; |
|
|
|
border-collapse: collapse; |
|
|
|
@ -198,19 +229,28 @@ |
|
|
|
</style> |
|
|
|
</style> |
|
|
|
</head> |
|
|
|
</head> |
|
|
|
<body> |
|
|
|
<body> |
|
|
|
<div class="main-layout"> |
|
|
|
<div class="results-input-container"> |
|
|
|
<div class="top-bar"> |
|
|
|
<div class="title-bar"> |
|
|
|
<div> |
|
|
|
<h2>Data input</h2> |
|
|
|
<label for="results-input">Paste one or more results below to add them</label> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<textarea id="results-input"></textarea> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="category-refinement"> |
|
|
|
<div> |
|
|
|
<div class="categories"></div> |
|
|
|
<label for="results-input">Paste one or more results below to add them</label> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="associations"> |
|
|
|
<textarea id="results-input"></textarea> |
|
|
|
<table class="association-table"></table> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="category-refinement"> |
|
|
|
|
|
|
|
<div class="title-bar"> |
|
|
|
|
|
|
|
<h2>Category refinement</h2> |
|
|
|
|
|
|
|
<button>Auto-merge</button> |
|
|
|
|
|
|
|
<button class="button-recalculate" disabled>Apply merged categories</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="categories"></div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="associations"> |
|
|
|
|
|
|
|
<div class="title-bar"> |
|
|
|
|
|
|
|
<h2>Category–card table</h2> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<table class="association-table"></table> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<template class="category-template"> |
|
|
|
<template class="category-template"> |
|
|
|
<div class="category"> |
|
|
|
<div class="category"> |
|
|
|
@ -322,7 +362,10 @@ |
|
|
|
resultCount: results.length, |
|
|
|
resultCount: results.length, |
|
|
|
}; |
|
|
|
}; |
|
|
|
for (const result of results) { |
|
|
|
for (const result of results) { |
|
|
|
for (const [categoryName, cards] of Object.entries(result)) { |
|
|
|
for (let [categoryName, cards] of Object.entries(result)) { |
|
|
|
|
|
|
|
if (categoryName in mergedCategories) { |
|
|
|
|
|
|
|
categoryName = mergedCategories[categoryName]; |
|
|
|
|
|
|
|
} |
|
|
|
if ( ! (categoryName in data.cardCountsByCategory)) { |
|
|
|
if ( ! (categoryName in data.cardCountsByCategory)) { |
|
|
|
data.cardCountsByCategory[categoryName] = {}; |
|
|
|
data.cardCountsByCategory[categoryName] = {}; |
|
|
|
data.categories.push({ |
|
|
|
data.categories.push({ |
|
|
|
@ -444,6 +487,9 @@ |
|
|
|
const categoriesContainer = document.querySelector('.category-refinement .categories'); |
|
|
|
const categoriesContainer = document.querySelector('.category-refinement .categories'); |
|
|
|
const categoryTemplate = document.querySelector('.category-template'); |
|
|
|
const categoryTemplate = document.querySelector('.category-template'); |
|
|
|
const cardTemplate = document.querySelector('.card-template'); |
|
|
|
const cardTemplate = document.querySelector('.card-template'); |
|
|
|
|
|
|
|
const recalculateButton = document.querySelector('.category-refinement .button-recalculate'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
recalculateButton.addEventListener('click', () => calculateAndRender()); |
|
|
|
|
|
|
|
|
|
|
|
function renderCategoryRefinement(data) { |
|
|
|
function renderCategoryRefinement(data) { |
|
|
|
const categoryElements = []; |
|
|
|
const categoryElements = []; |
|
|
|
@ -468,8 +514,20 @@ |
|
|
|
categoryElement.querySelector('.category-merge-button-end').addEventListener('click', handleMergeEnd); |
|
|
|
categoryElement.querySelector('.category-merge-button-end').addEventListener('click', handleMergeEnd); |
|
|
|
categoryElement.querySelector('.category-merge-button-cancel').addEventListener('click', handleMergeCancel); |
|
|
|
categoryElement.querySelector('.category-merge-button-cancel').addEventListener('click', handleMergeCancel); |
|
|
|
categoryElements.push(categoryElement); |
|
|
|
categoryElements.push(categoryElement); |
|
|
|
|
|
|
|
const thisCategoryMergedCategories = ( |
|
|
|
|
|
|
|
Object.entries(mergedCategories) |
|
|
|
|
|
|
|
.filter(([sourceName, targetName]) => targetName == category.name) |
|
|
|
|
|
|
|
.map(([sourceName, targetName]) => sourceName) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (thisCategoryMergedCategories.length > 0) { |
|
|
|
|
|
|
|
categoryElement.classList.add('has-merged-categories'); |
|
|
|
|
|
|
|
for (sourceCategoryName of thisCategoryMergedCategories) { |
|
|
|
|
|
|
|
renderMergeToolMergedCategory(categoryElement, {name: sourceCategoryName}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
categoriesContainer.replaceChildren(...categoryElements); |
|
|
|
categoriesContainer.replaceChildren(...categoryElements); |
|
|
|
|
|
|
|
updateRecalculateButton(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function handleMergeStart() { |
|
|
|
function handleMergeStart() { |
|
|
|
@ -493,6 +551,7 @@ |
|
|
|
targetCategoryElement.classList.add('has-merged-categories'); |
|
|
|
targetCategoryElement.classList.add('has-merged-categories'); |
|
|
|
renderMergeToolMergedCategory(targetCategoryElement, sourceCategory); |
|
|
|
renderMergeToolMergedCategory(targetCategoryElement, sourceCategory); |
|
|
|
mergedCategories[sourceCategory.name] = targetCategory.name; |
|
|
|
mergedCategories[sourceCategory.name] = targetCategory.name; |
|
|
|
|
|
|
|
updateRecalculateButton(); |
|
|
|
} |
|
|
|
} |
|
|
|
function handleMergeUndo() { |
|
|
|
function handleMergeUndo() { |
|
|
|
const undoButton = this; |
|
|
|
const undoButton = this; |
|
|
|
@ -500,15 +559,29 @@ |
|
|
|
const targetCategory = targetCategoryElement._category; |
|
|
|
const targetCategory = targetCategoryElement._category; |
|
|
|
const sourceCategory = undoButton._sourceCategory; |
|
|
|
const sourceCategory = undoButton._sourceCategory; |
|
|
|
const sourceCategoryElement = sourceCategory.element; |
|
|
|
const sourceCategoryElement = sourceCategory.element; |
|
|
|
if (sourceCategoryElement.classList.contains('is-merge-pending')) { |
|
|
|
if (sourceCategoryElement) { |
|
|
|
sourceCategoryElement.classList.remove('is-merge-pending'); |
|
|
|
if (sourceCategoryElement.classList.contains('is-merge-pending')) { |
|
|
|
|
|
|
|
sourceCategoryElement.classList.remove('is-merge-pending'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
undoButton.closest('.merged-category').remove(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
undoButton.closest('.merged-category').classList.add('is-undo-pending'); |
|
|
|
} |
|
|
|
} |
|
|
|
delete mergedCategories[sourceCategory.name]; |
|
|
|
|
|
|
|
const hasMoreMergedCategories = Object.values(mergedCategories).includes(targetCategory.name); |
|
|
|
const hasMoreMergedCategories = Object.values(mergedCategories).includes(targetCategory.name); |
|
|
|
if ( ! hasMoreMergedCategories) { |
|
|
|
if ( ! hasMoreMergedCategories) { |
|
|
|
targetCategoryElement.classList.remove('has-merged-categories'); |
|
|
|
targetCategoryElement.classList.remove('has-merged-categories'); |
|
|
|
} |
|
|
|
} |
|
|
|
undoButton.closest('.merged-category').remove(); |
|
|
|
delete mergedCategories[sourceCategory.name]; |
|
|
|
|
|
|
|
updateRecalculateButton(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
function updateRecalculateButton() { |
|
|
|
|
|
|
|
if (document.querySelector('.is-merge-pending, .is-undo-pending')) { |
|
|
|
|
|
|
|
recalculateButton.disabled = false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
recalculateButton.disabled = true; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function renderMergeToolMergedCategory(targetCategoryElement, sourceCategory) { |
|
|
|
function renderMergeToolMergedCategory(targetCategoryElement, sourceCategory) { |
|
|
|
|