diff --git a/app/controllers/GalleryController.scala b/app/controllers/GalleryController.scala index b565b52cb9..6ea1205932 100644 --- a/app/controllers/GalleryController.scala +++ b/app/controllers/GalleryController.scala @@ -44,10 +44,11 @@ class GalleryController @Inject() (implicit val env: Environment[User, SessionAu val regionIds: Set[Int] = submission.regionIds.getOrElse(Seq()).toSet val severities: Set[Int] = submission.severities.getOrElse(Seq()).toSet val tags: Set[String] = submission.tags.getOrElse(Seq()).toSet + val order: Int = submission.order // Get labels from LabelTable. val labels: Seq[LabelValidationMetadata] = - LabelTable.getGalleryLabels(n, labelTypeId, loadedLabelIds, valOptions, regionIds, severities, tags, user.userId) + LabelTable.getGalleryLabels(n, labelTypeId, loadedLabelIds, valOptions, regionIds, severities, tags, user.userId, order) val jsonList: Seq[JsObject] = labels.map(l => Json.obj( "label" -> LabelFormat.validationLabelMetadataToJson(l), diff --git a/app/formats/json/GalleryFormats.scala b/app/formats/json/GalleryFormats.scala index 3ad5aac087..b8248d7eb2 100644 --- a/app/formats/json/GalleryFormats.scala +++ b/app/formats/json/GalleryFormats.scala @@ -8,7 +8,7 @@ object GalleryFormats { case class GalleryEnvironmentSubmission(browser: Option[String], browserVersion: Option[String], browserWidth: Option[Int], browserHeight: Option[Int], screenWidth: Option[Int], screenHeight: Option[Int], availWidth: Option[Int], availHeight: Option[Int], operatingSystem: Option[String], language: String) case class GalleryInteractionSubmission(action: String, panoId: Option[String], note: Option[String], timestamp: Long) case class GalleryTaskSubmission(environment: GalleryEnvironmentSubmission, interactions: Seq[GalleryInteractionSubmission]) - case class GalleryLabelsRequest(n: Int, labelTypeId: Option[Int], validationOptions: Option[Seq[String]], regionIds: Option[Seq[Int]], severities: Option[Seq[Int]], tags: Option[Seq[String]], loadedLabels: Seq[Int]) + case class GalleryLabelsRequest(n: Int, labelTypeId: Option[Int], validationOptions: Option[Seq[String]], regionIds: Option[Seq[Int]], severities: Option[Seq[Int]], tags: Option[Seq[String]], loadedLabels: Seq[Int], order: Int) implicit val galleryEnvironmentSubmissionReads: Reads[GalleryEnvironmentSubmission] = ( (JsPath \ "browser").readNullable[String] and @@ -42,6 +42,7 @@ object GalleryFormats { (JsPath \ "neighborhoods").readNullable[Seq[Int]] and (JsPath \ "severities").readNullable[Seq[Int]] and (JsPath \ "tags").readNullable[Seq[String]] and - (JsPath \ "loaded_labels").read[Seq[Int]] + (JsPath \ "loaded_labels").read[Seq[Int]] and + (JsPath \ "order").read[Int] )(GalleryLabelsRequest.apply _) } diff --git a/app/models/label/LabelTable.scala b/app/models/label/LabelTable.scala index 9a255c4f96..067329cdaa 100644 --- a/app/models/label/LabelTable.scala +++ b/app/models/label/LabelTable.scala @@ -643,9 +643,10 @@ object LabelTable { * @param regionIds Set of neighborhoods to get labels from. All neighborhoods if empty. * @param severity Set of severities the labels grabbed can have. * @param tags Set of tags the labels grabbed can have. + * @param order Order to grab labels by * @return Seq[LabelValidationMetadata] */ - def getGalleryLabels(n: Int, labelTypeId: Option[Int], loadedLabelIds: Set[Int], valOptions: Set[String], regionIds: Set[Int], severity: Set[Int], tags: Set[String], userId: UUID): Seq[LabelValidationMetadata] = db.withSession { implicit session => + def getGalleryLabels(n: Int, labelTypeId: Option[Int], loadedLabelIds: Set[Int], valOptions: Set[String], regionIds: Set[Int], severity: Set[Int], tags: Set[String], userId: UUID, order: Int): Seq[LabelValidationMetadata] = db.withSession { implicit session => // Filter labels based on correctness. val _l1 = if (!valOptions.contains("correct")) labels.filter(l => l.correct.isEmpty || !l.correct) else labels val _l2 = if (!valOptions.contains("incorrect")) _l1.filter(l => l.correct.isEmpty || l.correct) else _l1 @@ -691,24 +692,63 @@ object LabelTable { // Remove duplicates that we got from joining with the `label_tag` table. val _uniqueLabels = if (tags.nonEmpty) _labelInfoWithUserVals.groupBy(x => x).map(_._1) else _labelInfoWithUserVals - // Randomize, check for GSV imagery, & add tag info. If no label type is specified, do it by label type. + // Sort by order code, check for GSV imagery, & add tag info. If no label type is specified, do it by label type. if (labelTypeId.isDefined) { - val rand = SimpleFunction.nullary[Double]("random") - val _randomizedLabels = _uniqueLabels.sortBy(x => rand).list.map(LabelValidationMetadataWithoutTags.tupled) + val _assortedLabels = _uniqueLabels.list.map(LabelValidationMetadataWithoutTags.tupled) + + // Sort label by order code + order match { + case 0 => _assortedLabels.sortBy(_.severity).reverse + case 1 => _assortedLabels.sortBy(_.severity) + case 2 => _assortedLabels.sortBy(_.timestamp).reverse + case 3 => _assortedLabels.sortBy(_.timestamp) + case 4 => _assortedLabels.sortBy(_.agreeCount).reverse + case 5 => _assortedLabels.sortBy(_.disagreeCount) + case 6 => _assortedLabels.sortBy(label => getTagsFromLabelId(label.labelId).length).reverse + case 7 => _assortedLabels.sortBy(label => getTagsFromLabelId(label.labelId).length) + case -1 => scala.util.Random.shuffle(_assortedLabels) + } // Take the first `n` labels with non-expired GSV imagery. - checkForGsvImagery(_randomizedLabels, n) + checkForGsvImagery(_assortedLabels, n) .map(l => labelAndTagsToLabelValidationMetadata(l, getTagsFromLabelId(l.labelId))) } else { val _potentialLabels: Map[String, List[LabelValidationMetadataWithoutTags]] = + // _labelInfoWithUserVals.list.map(LabelValidationMetadataWithoutTags.tupled) - .groupBy(_.labelType).map(l => l._1 -> scala.util.Random.shuffle(l._2)) + .groupBy(_.labelType) + .map(l => l._1 -> scala.util.Random.shuffle(l._2)) + + // Sort individual labels by order code first + order match { + case 0 => _potentialLabels.mapValues(labels => labels.sortBy(_.severity).reverse) + case 1 => _potentialLabels.mapValues(labels => labels.sortBy(_.severity)) + case 2 => _potentialLabels.mapValues(labels => labels.sortBy(_.timestamp).reverse) + case 3 => _potentialLabels.mapValues(labels => labels.sortBy(_.timestamp)) + case 4 => _potentialLabels.mapValues(labels => labels.sortBy(_.agreeCount).reverse) + case 5 => _potentialLabels.mapValues(labels => labels.sortBy(_.disagreeCount)) + case 6 => _potentialLabels.mapValues(labels => labels.sortBy(label => getTagsFromLabelId(label.labelId).length).reverse) + case 7 => _potentialLabels.mapValues(labels => labels.sortBy(label => getTagsFromLabelId(label.labelId).length)) + case -1 => _potentialLabels.mapValues(labels => scala.util.Random.shuffle(labels)) + } val nPerType: Int = n / LabelTypeTable.primaryLabelTypes.size - // Take the first `nPerType` labels with non-expired GSV imagery for each label type, then randomize them. + // Take the first `nPerType` labels with non-expired GSV imagery for each label type, then sort them. val chosenLabels: Seq[LabelValidationMetadata] = checkForImageryByLabelType(_potentialLabels, nPerType) .map(l => labelAndTagsToLabelValidationMetadata(l, getTagsFromLabelId(l.labelId))) - scala.util.Random.shuffle(chosenLabels) + + // Now resort based on all labels by order code + order match { + case 0 => chosenLabels.sortBy(_.severity).reverse + case 1 => chosenLabels.sortBy(_.severity) + case 2 => chosenLabels.sortBy(_.timestamp).reverse + case 3 => chosenLabels.sortBy(_.timestamp) + case 4 => chosenLabels.sortBy(_.agreeCount).reverse + case 5 => chosenLabels.sortBy(_.disagreeCount) + case 6 => chosenLabels.sortBy(_.tags.length).reverse + case 7 => chosenLabels.sortBy(_.tags.length) + case -1 => scala.util.Random.shuffle(chosenLabels) + } } } diff --git a/app/views/gallery.scala.html b/app/views/gallery.scala.html index 0ba2d15942..478e99fb14 100644 --- a/app/views/gallery.scala.html +++ b/app/views/gallery.scala.html @@ -74,6 +74,56 @@
@Messages("validatio +
+

@Messages("gallery.sort.by")

+
+
+
@Messages("gallery.severity")
+
+ + +
+
+
+
@Messages("gallery.recency")
+
+ + +
+
+
+
@Messages("gallery.validation")
+
+ + +
+
+
+
@Messages("gallery.tags")
+
+ + +
+
+
+
+
diff --git a/conf/messages.de b/conf/messages.de index 610e7ee005..dd01fb8f2c 100644 --- a/conf/messages.de +++ b/conf/messages.de @@ -358,6 +358,12 @@ gallery.all = Alle Beschriftungskategorien gallery.labels.not.found = Keine Treffer. Erkundung starten, um mehr Daten beizutragen! gallery.cards = Beschriftungen werden zufällig sortiert, basierend auf gewählten Filtern gallery.clear.filters = Filter löschen +gallery.sort.by = Sorteer Nach +gallery.severity = Schweregrad +gallery.recency = Zeitstempel +gallery.validation = Validierung +gallery.tags = Tags +gallery.clear.sorting = Sortierung löschen routebuilder.name = RouteBuilder routebuilder.welcome = Willkommen bei RouteBuilder diff --git a/conf/messages.en b/conf/messages.en index 5b4a104330..d756659b34 100644 --- a/conf/messages.en +++ b/conf/messages.en @@ -353,6 +353,12 @@ gallery.all = All Label Types gallery.labels.not.found = No matches. Start exploring to contribute more data! gallery.cards = Labels are sorted randomly based on selected filters gallery.clear.filters = Clear Filters +gallery.sort.by = Sort By +gallery.severity = Severity +gallery.recency = Time Stamp +gallery.validation = Validation +gallery.tags = Tags +gallery.clear.sorting = Clear Sorting routebuilder.name = RouteBuilder routebuilder.welcome = Welcome to RouteBuilder diff --git a/conf/messages.es b/conf/messages.es index fca31ec9f0..20e3fbc9de 100644 --- a/conf/messages.es +++ b/conf/messages.es @@ -360,6 +360,12 @@ gallery.all = Todos los tipos de etiquetas gallery.labels.not.found = No hay resultados. ¡Comienza a explorar para aportar más datos! gallery.cards = Las etiquetas se ordenan aleatoriamente según los filtros seleccionados gallery.clear.filters = Borrar Filtros +gallery.sort.by = Ordenar por +gallery.severity = Gravedad +gallery.recency= Marca de tiempo +gallery.validation = Validación +gallery.tags = Etiquetas +gallery.clear.sorting = Borrar Clasificación routebuilder.name = Constructor de rutas routebuilder.welcome = Bienvenido a RouteBuilder diff --git a/conf/messages.nl b/conf/messages.nl index 669b5e0279..2e7a0faf6b 100644 --- a/conf/messages.nl +++ b/conf/messages.nl @@ -360,6 +360,12 @@ gallery.all = Alle labeltypen gallery.labels.not.found = Geen overeenkomsten. Begin met verkennen om meer data bij te dragen! gallery.cards = Labels worden willekeurig gesorteerd op basis van geselecteerde filters gallery.clear.filters = Filters Wissen +gallery.sort.by = Sorteer op +gallery.severity = Ernst +gallery.recency = Tijdstempel +gallery.validation = Validatie +gallery.tags = Labels +gallery.clear.sorting = Duidelijke Sortering routebuilder.name = Routebuilder routebuilder.welcome = Welkom bij Routebuilder diff --git a/conf/messages.zh-TW b/conf/messages.zh-TW index adfdf613d6..b6ec15fd19 100644 --- a/conf/messages.zh-TW +++ b/conf/messages.zh-TW @@ -384,6 +384,12 @@ gallery.all = 所有的標記類型 gallery.labels.not.found = 無相符資料。 開始探索以蒐集更多的資料! gallery.cards = 所有標記依據所選定的過濾器而隨機排序 gallery.clear.filters = 清除過濾器 +gallery.sort.by = 排序方式 +gallery.severity = 嚴重程度 +gallery.recency = 时间戳 +gallery.validation = 验证 +gallery.tags = 标签 +gallery.clear.sorting = 清除排序 routebuilder.name = 路線設立工具 routebuilder.welcome = 歡迎使用路線設立工具 diff --git a/public/javascripts/Gallery/css/filter.css b/public/javascripts/Gallery/css/filter.css index 6152e20819..e4e5ecd009 100644 --- a/public/javascripts/Gallery/css/filter.css +++ b/public/javascripts/Gallery/css/filter.css @@ -91,3 +91,49 @@ margin-top: 5px; color: darkgray; } + +/* added new parts for card sort menu*/ + +#card-sort-menu-holder { + display: flex; + flex-direction: row; + align-items: center; + margin-top: 5px; + background-color: transparent; + border-color: transparent; +} + +.card-sort-select button { + background-color: transparent; + border-color: transparent; + cursor: pointer; + display: flex; + flex-direction: column; +} + +.card-sort-select-option{ + display: flex; + flex-direction: row; +} + +.icon { + width: 20px; + height: 15px; +} + +.active { + stroke-width: 102.4; +} + +#clear-sorting { + background-color: transparent; + border-color: transparent; + cursor: pointer; + display: none +} + +#clear-sorting h6 { + margin-top: 5px; + color: darkgray; +} + diff --git a/public/javascripts/Gallery/src/Main.js b/public/javascripts/Gallery/src/Main.js index 2a1c58ec9f..fdda771de3 100644 --- a/public/javascripts/Gallery/src/Main.js +++ b/public/javascripts/Gallery/src/Main.js @@ -51,6 +51,7 @@ function Main (params) { sg.ui.cardContainer.prevPage = $("#prev-page"); sg.ui.cardContainer.pageNumber = $("#page-number") sg.ui.cardContainer.nextPage = $("#next-page"); + sg.ui.cardContainer.clearSorting = $("#clear-sorting"); // Keep track of some other elements whose status or dimensions are useful. sg.ui.pageControl = $(".page-control"); @@ -72,7 +73,7 @@ function Main (params) { sg.cityMenu = new CityMenu(sg.ui.cityMenu); sg.labelTypeMenu = new LabelTypeMenu(sg.ui.labelTypeMenu, params.initialFilters.labelType); - // sg.cardSortMenu = new CardSortMenu(sg.ui.cardSortMenu); + sg.cardSortMenu = new CardSortMenu(sg.ui.cardSortMenu); sg.cardFilter = new CardFilter(sg.ui.cardFilter, sg.labelTypeMenu, sg.cityMenu, params.initialFilters); sg.cardContainer = new CardContainer(sg.ui.cardContainer, params.initialFilters); sg.modal = sg.cardContainer.getModal; @@ -91,7 +92,7 @@ function Main (params) { if (!sg.pageLoading.is(":visible") && !sg.labelsNotFound.is(':visible')) { let sidebarBottomOffset = sidebarWrapper.offset().top + sidebarWrapper.outerHeight(true); let cardContainerBottomOffset = sg.ui.cardContainer.holder.offset().top + - sg.ui.cardContainer.holder.outerHeight(true) - 5; + sg.ui.cardContainer.holder.outerHeight(true) - 175; let visibleWindowBottomOffset = $(window).scrollTop() + $(window).height(); // Handle sidebar stickiness. diff --git a/public/javascripts/Gallery/src/cards/Card.js b/public/javascripts/Gallery/src/cards/Card.js index a7959f4080..2039a2aafe 100644 --- a/public/javascripts/Gallery/src/cards/Card.js +++ b/public/javascripts/Gallery/src/cards/Card.js @@ -189,6 +189,15 @@ function Card (params, imageUrl, modal) { return status; } + /** + * Returns the ratio of agree to disagree validations. + * @returns {number} Ratio of agree to disagree validations. + */ + function getValidatonRatio() { + if (properties.val_counts['Agree'] + properties.val_counts['Disagree'] === 0) return 1; + return properties.val_counts['Agree'] / (properties.val_counts['Agree'] + properties.val_counts['Disagree']); + } + /** * Loads the pano image from url. */ @@ -292,6 +301,7 @@ function Card (params, imageUrl, modal) { self.getProperties = getProperties; self.getProperty = getProperty; self.getStatus = getStatus; + self.getValidatonRatio = getValidatonRatio self.loadImage = loadImage; self.render = render; self.setProperty = setProperty; diff --git a/public/javascripts/Gallery/src/cards/CardContainer.js b/public/javascripts/Gallery/src/cards/CardContainer.js index 9e2601f560..d721677417 100644 --- a/public/javascripts/Gallery/src/cards/CardContainer.js +++ b/public/javascripts/Gallery/src/cards/CardContainer.js @@ -6,6 +6,7 @@ * @returns {CardContainer} * @constructor */ + function CardContainer(uiCardContainer, initialFilters) { let self = this; @@ -16,11 +17,20 @@ function CardContainer(uiCardContainer, initialFilters) { const cardsPerLine = 3; const cardPadding = 25; + // Number of cards before current sort. + let cardsBeforeCurrentSort = 0; + // TODO: Possibly remove if any type of sorting is no longer wanted. let status = { - order: 0 + order: -1 }; + //create a set of the order codes 0 to 7 to use for initial type sorting + let orderCodes = new Set([0, 1, 2, 3, 4, 5, 6, 7]); + + // Initial sort of cards. + let initialSort = true; + // Map label type to id. let labelTypeIds = { CurbRamp: 1, @@ -82,10 +92,11 @@ function CardContainer(uiCardContainer, initialFilters) { cardsByType[currentLabelType] = new CardBucket(); // Grab first batch of labels to show. - fetchLabels(labelTypeIds[currentLabelType], initialLoad, initialFilters.validationOptions, Array.from(loadedLabelIds), initialFilters.neighborhoods, initialFilters.severities, initialFilters.tags, function() { + fetchLabels(labelTypeIds[currentLabelType], initialLoad, initialFilters.validationOptions, Array.from(loadedLabelIds), initialFilters.neighborhoods, initialFilters.severities, initialFilters.tags, getStatus().order, function() { currentCards = cardsByType[currentLabelType].copy(); render(); }); + // Creates the Modal object in the DOM element currently present. modal = new Modal($('.gallery-modal')); // Add the click event for opening the Modal when a card is clicked. @@ -106,7 +117,7 @@ function CardContainer(uiCardContainer, initialFilters) { /** * Find the card which contains the image with the same imageID as supplied. - * + * * @param {String} id The id of the image Id to find * @returns {Card} finds the matching card and returns it */ @@ -141,7 +152,7 @@ function CardContainer(uiCardContainer, initialFilters) { }); setPage(currentPage + 1); sg.ui.cardContainer.prevPage.prop("disabled", false); - updateCardsNewPage(); + updateCardsNewPage(getStatus().order); } function handlePrevPageClick() { @@ -152,7 +163,7 @@ function CardContainer(uiCardContainer, initialFilters) { }); $("#next-page").prop("disabled", false); setPage(currentPage - 1); - updateCardsNewPage(); + updateCardsNewPage(getStatus().order); } } @@ -174,9 +185,10 @@ function CardContainer(uiCardContainer, initialFilters) { * @param {*} neighborhoods Region IDs the labels to be grabbed can be from (Set to undefined if N/A). * @param {*} severities Severities the labels to be grabbed can have (Set to undefined if N/A). * @param {*} tags Tags the labels to be grabbed can have (Set to undefined if N/A). + * @param {*} order Order in which the labels are to be sorted. * @param {*} callback Function to be called when labels arrive. */ - function fetchLabels(labelTypeId, n, validationOptions, loadedLabels, neighborhoods, severities, tags, callback) { + function fetchLabels(labelTypeId, n, validationOptions, loadedLabels, neighborhoods, severities, tags, order, callback) { var url = "/label/labels"; let data = { label_type_id: labelTypeId, @@ -185,7 +197,9 @@ function CardContainer(uiCardContainer, initialFilters) { ...(neighborhoods !== undefined && { neighborhoods: neighborhoods }), ...(severities !== undefined && { severities: severities }), ...(tags !== undefined && { tags: tags }), - loaded_labels: loadedLabels + loaded_labels: loadedLabels, + order: order + } $.ajax({ async: true, @@ -238,9 +252,14 @@ function CardContainer(uiCardContainer, initialFilters) { /** * Updates Cards being shown when user moves to next/previous page. */ - function updateCardsNewPage() { + function updateCardsNewPage(order) { refreshUI(); + // set initial sort to false if not called yet + if (initialSort === true) { + initialSort = false; + } + let appliedTags = sg.cardFilter.getAppliedTagNames(); let appliedSeverities = sg.cardFilter.getAppliedSeverities(); let appliedValOptions = sg.cardFilter.getAppliedValidationOptions(); @@ -254,17 +273,19 @@ function CardContainer(uiCardContainer, initialFilters) { currentCards.filterOnSeverities(appliedSeverities); currentCards.filterOnValidationOptions(appliedValOptions); - if (currentCards.getSize() < cardsPerPage * currentPage + 1) { - // When we don't have enough cards of specific query to show on one page, see if more can be grabbed. - fetchLabels(labelTypeIds[currentLabelType], cardsPerPage * 2, appliedValOptions, Array.from(loadedLabelIds), initialFilters.neighborhoods, appliedSeverities, appliedTags, function() { + if (currentCards.getSize() - cardsBeforeCurrentSort < cardsPerPage * currentPage + 1) { + // When we don't have enough cards of specific query or sort to show on one page, see if more can be grabbed. + fetchLabels(labelTypeIds[currentLabelType], cardsPerPage * 2, appliedValOptions, Array.from(loadedLabelIds), initialFilters.neighborhoods, appliedSeverities, appliedTags, order,function() { currentCards = cardsByType[currentLabelType].copy(); currentCards.filterOnTags(appliedTags); currentCards.filterOnSeverities(appliedSeverities); currentCards.filterOnValidationOptions(appliedValOptions); + sortCards(getStatus().order); lastPage = currentCards.getCards().length <= currentPage * cardsPerPage; render(); }); } else { + sortCards(getStatus().order); lastPage = false; render(); } @@ -282,20 +303,122 @@ function CardContainer(uiCardContainer, initialFilters) { } setPage(1); - updateCardsNewPage(); + updateCardsNewPage(getStatus().order); } + /** + * Toggles the arrow of the sort menu. + * @param order The order of the sort. + */ + function toggleArrow(order) { + let icon = document.getElementById('icon' + order); + document.querySelectorAll('.icon').forEach((el) => { + el.classList.remove('active'); + }); + icon.classList.toggle('active'); + } + + /** + * Sorts the cards based on the order. + * @param order The order of the sort. + */ function sortCards(order) { - // uiCardContainer.holder.empty(); - // currentCards.sort((card1, card2) => sg.cardSortMenu.getStatus().severity * card1.getProperty("severity") - card2.getProperty("severity")); - // - // render(); - // console.log("sort cards in card container called"); - // // Write a sorting query for backend - // setStatus("order", order); - // render(); + uiCardContainer.holder.empty(); + if (order != -1) { + uiCardContainer.clearSorting.show(); + } + + // check if order is in set for first time sorting + if (orderCodes.has(order)) { + orderCodes.delete(order); + initialSort = true; + } + + // Grab more cards on intial page load. + if (initialSort === true) { + let labelType = sg.cardFilter.getStatus().currentLabelType; + let tags = sg.cardFilter.getAppliedTagNames(); + let severities = sg.cardFilter.getAppliedSeverities(); + let validationOptions = sg.cardFilter.getAppliedValidationOptions(); + + fetchLabels(labelTypeIds[labelType], cardsPerPage * 2, validationOptions, Array.from(loadedLabelIds), initialFilters.neighborhoods, severities, tags, order, function() { + currentCards = cardsByType[labelType].copy(); + currentCards.filterOnTags(tags); + currentCards.filterOnSeverities(severities); + currentCards.filterOnValidationOptions(validationOptions); + lastPage = currentCards.getCards().length <= currentPage * cardsPerPage; + }); + initialSort = false; + } + + // severity sort + if (order === 0 || order === 1) { + if (order === 0) { + currentCards.getCards().sort((card1, card2) => card2.getProperty("severity") - card1.getProperty("severity")); + } else { + currentCards.getCards().sort((card1, card2) => card1.getProperty("severity") - card2.getProperty("severity")); + } + } + + // recency sort + if (order === 2 || order === 3) { + if (order === 3) { + currentCards.getCards().sort((card1, card2) => card1.getProperty("label_timestamp") - card2.getProperty("label_timestamp")); + + } else { + currentCards.getCards().sort((card1, card2) => card2.getProperty("label_timestamp") - card1.getProperty("label_timestamp")); + } + } + + // validation sort + if (order === 4 || order === 5) { + if (order === 4) { + currentCards.getCards().sort((card1, card2) => card2.getValidatonRatio() - card1.getValidatonRatio()); + } else { + currentCards.getCards().sort((card1, card2) => card1.getValidatonRatio() - card2.getValidatonRatio()); + } + } + + // tags sort + if (order === 6 || order === 7) { + if (order === 6) { + currentCards.getCards().sort((card1, card2) => card2.getProperty("tags").length - card1.getProperty("tags").length); + } else { + currentCards.getCards().sort((card1, card2) => card1.getProperty("tags").length - card2.getProperty("tags").length); + } + } + + // When sort order is changed, reset page, and card sort count + if (getStatus().order !== order) { + toggleArrow(order); + cardsBeforeCurrentSort = currentCards.getSize(); + if (currentPage !== 1) { + setPage(1); + } + } + + setStatus("order", order); + render(); } + /** + * Clear sorting order. + */ + function clearSorting() { + initialSort = true; + setStatus("order", -1); + } + + // Clear sorting button + uiCardContainer.clearSorting.on('click', function() { + clearSorting(); + uiCardContainer.clearSorting.hide(); + document.querySelectorAll('.icon').forEach((el) => { + el.classList.remove('active'); + }); + render() + }); + /** * Renders current cards. */ @@ -372,6 +495,10 @@ function CardContainer(uiCardContainer, initialFilters) { } } + function getStatus() { + return status; + } + /** * Flush all Cards currently being rendered. */ @@ -438,6 +565,7 @@ function CardContainer(uiCardContainer, initialFilters) { self.push = push; self.updateCardsByFilter = updateCardsByFilter; self.updateCardsNewPage = updateCardsNewPage; + self.toggleArrow = toggleArrow; self.sortCards = sortCards; self.render = render; self.clearCurrentCards = clearCurrentCards; diff --git a/public/javascripts/Gallery/src/filter/CardSortMenu.js b/public/javascripts/Gallery/src/filter/CardSortMenu.js index 532ae0848d..aae37d38fa 100644 --- a/public/javascripts/Gallery/src/filter/CardSortMenu.js +++ b/public/javascripts/Gallery/src/filter/CardSortMenu.js @@ -9,14 +9,23 @@ function CardSortMenu(uiCardSortMenu) { // The code values associated with each sort. let orderCodes = { - sort_LeastSevere: 0, - sort_MostSevere: 1 + mostSevere: 0, + leastSevere: 1, + + mostRecent: 2, + leastRecent: 3, + + mostValidation: 4, + leastValidation: 5, + + mostTags: 6, + leastTags: 7, } // The status of the sorting at any point. let status = { - severity: 1, - sortType: "none" + // severity: 1, + // sortType: "none" }; function _init() {