From 7aebcaa1ef899201ffe0dc06559e0f2da1dae1c2 Mon Sep 17 00:00:00 2001 From: Arild Matsson Date: Wed, 2 Oct 2024 18:45:09 +0200 Subject: [PATCH] refactor: reduce-select as component --- app/scripts/components/reduce-select.ts | 176 ++++++++++++++++++++++++ app/scripts/components/searchtabs.js | 15 +- app/scripts/directives/reduce-select.ts | 155 --------------------- app/styles/styles.scss | 5 +- 4 files changed, 188 insertions(+), 163 deletions(-) create mode 100644 app/scripts/components/reduce-select.ts delete mode 100644 app/scripts/directives/reduce-select.ts diff --git a/app/scripts/components/reduce-select.ts b/app/scripts/components/reduce-select.ts new file mode 100644 index 000000000..53788ad3c --- /dev/null +++ b/app/scripts/components/reduce-select.ts @@ -0,0 +1,176 @@ +/** @format */ +import _ from "lodash" +import angular, { IController, IScope, ITimeoutService } from "angular" +import { html } from "@/util" +import { AttributeOption } from "@/corpus_listing" + +type ReduceSelectScope = IScope & { + keyItems: Record + hasWordAttrs: boolean + hasStructAttrs: boolean + toggleSelected: (value: string, event: MouseEvent) => void + toggleWordInsensitive: (event: MouseEvent) => void + toggled: (open: boolean) => void +} + +type Item = AttributeOption & { + selected?: boolean + insensitive?: boolean +} + +type ReduceSelectController = IController & { + items: Item[] + selected: string[] + insensitive: string[] + onChange: (value: { selected?: string[]; insensitive?: string[] }) => void +} + +angular.module("korpApp").component("reduceSelect", { + template: html`
+
+
+ {{ "reduce_text" | loc:$root.lang }}: + {{keyItems[$ctrl.selected[0]].label | locObj:$root.lang}} + (+{{ $ctrl.selected.length - 1 }}) + +
+
+
+
    +
  • + + {{keyItems['word'].label | locObj:$root.lang }} + Aa +
  • + {{'word_attr' | loc:$root.lang}} +
  • + + {{item.label | locObj:$root.lang }} +
  • + {{'sentence_attr' | loc:$root.lang}} +
  • + + {{item.label | locObj:$root.lang }} +
  • +
+
+
`, + bindings: { + items: "<", + selected: "<", + insensitive: "<", + onChange: "<", + }, + controller: [ + "$scope", + function (scope: ReduceSelectScope) { + const $ctrl = this as ReduceSelectController + + $ctrl.$onChanges = (changes) => { + if ("items" in changes && $ctrl.items) { + scope.keyItems = _.keyBy($ctrl.items, "value") + scope.hasWordAttrs = $ctrl.items.some((item) => item.group == "word_attr") + scope.hasStructAttrs = $ctrl.items.some((item) => item.group == "sentence_attr") + } + + for (const name of $ctrl.selected || []) { + if (name in scope.keyItems) scope.keyItems[name].selected = true + } + + for (const name of $ctrl.insensitive || []) { + if (name in scope.keyItems) scope.keyItems[name].insensitive = true + } + + // Only after initialization + if ($ctrl.items && $ctrl.selected && $ctrl.insensitive) validate() + } + + /** Report any changes upwards */ + function updateSelected() { + validate() + + const selected = $ctrl.items.filter((item) => item.selected).map((item) => item.value) + const insensitive = $ctrl.items.filter((item) => item.insensitive).map((item) => item.value) + + $ctrl.onChange({ + // Only set values that have changed + selected: !_.isEqual(selected, $ctrl.selected) ? selected : undefined, + insensitive: !_.isEqual(insensitive, $ctrl.insensitive) ? insensitive : undefined, + }) + } + + /** Fix state inconsistencies */ + function validate() { + // If no selection given, default to selecting the word option + const hasSelection = $ctrl.items.some((item) => item.selected) + if (!hasSelection) { + scope.keyItems["word"].selected = true + } + + // If unselecting the word option, reset the insensitive flag. + if (!scope.keyItems["word"].selected) { + scope.keyItems["word"].insensitive = false + } + } + + scope.toggleSelected = function (value, event) { + event.stopPropagation() + const item = scope.keyItems[value] + const isLinux = window.navigator.userAgent.indexOf("Linux") !== -1 + if (isLinux ? event.ctrlKey : event.altKey) { + // Unselect all options and select only the given option + $ctrl.items.forEach((item) => (item.selected = false)) + item.selected = true + } else { + item.selected = !item.selected + } + updateSelected() + } + + scope.toggleWordInsensitive = function (event) { + event.stopPropagation() + scope.keyItems["word"].insensitive = !scope.keyItems["word"].insensitive + if (!scope.keyItems["word"].selected) { + scope.keyItems["word"].selected = true + } + updateSelected() + } + + scope.toggled = function (open) { + // if no element is selected when closing popop, select word + if (!open && !$ctrl.selected.length) { + scope.keyItems["word"].selected = true + updateSelected() + } + } + }, + ], +}) diff --git a/app/scripts/components/searchtabs.js b/app/scripts/components/searchtabs.js index 1d2126c7b..d586eed25 100644 --- a/app/scripts/components/searchtabs.js +++ b/app/scripts/components/searchtabs.js @@ -10,8 +10,8 @@ import "@/components/extended/extended-standard" import "@/components/extended/extended-parallel" import "@/components/advanced-search" import "@/components/compare-search" +import "@/components/reduce-select" import "@/directives/click-cover" -import "@/directives/reduce-select" import "@/directives/tab-hash" angular.module("korpApp").component("searchtabs", { @@ -63,11 +63,9 @@ angular.module("korpApp").component("searchtabs", { {{'statistics' | loc:$root.lang}}: @@ -192,7 +190,10 @@ angular.module("korpApp").component("searchtabs", { } }) - $ctrl.reduceOnChange = () => { + $ctrl.reduceOnChange = ({ selected, insensitive }) => { + if (selected) $ctrl.statSelectedAttrs = selected + if (insensitive) $ctrl.statInsensitiveAttrs = insensitive + if ($ctrl.statSelectedAttrs && $ctrl.statSelectedAttrs.length > 0) { if ($ctrl.statSelectedAttrs.length != 1 || !$ctrl.statSelectedAttrs.includes("word")) { $location.search("stats_reduce", $ctrl.statSelectedAttrs.join(",")) diff --git a/app/scripts/directives/reduce-select.ts b/app/scripts/directives/reduce-select.ts deleted file mode 100644 index 9c74ba9c6..000000000 --- a/app/scripts/directives/reduce-select.ts +++ /dev/null @@ -1,155 +0,0 @@ -/** @format */ -import _ from "lodash" -import angular, { IScope, ITimeoutService } from "angular" -import { html } from "@/util" -import { AttributeOption } from "@/corpus_listing" - -type ReduceSelectScope = IScope & { - items: Item[] - keyItems: Record - hasWordAttrs: boolean - hasStructAttrs: boolean - selected: string[] - insensitive: string[] - toggleSelected: (value: string, event: MouseEvent) => void - toggleWordInsensitive: (event: MouseEvent) => void - onChange: () => void - toggled: (open: boolean) => void -} - -type Item = AttributeOption & { - selected?: boolean - insensitive?: boolean -} - -angular.module("korpApp").directive("reduceSelect", [ - "$timeout", - ($timeout: ITimeoutService) => ({ - restrict: "AE", - scope: { - items: "=reduceItems", - selected: "=reduceSelected", - insensitive: "=reduceInsensitive", - lang: "=reduceLang", - onChange: "<", - }, - replace: true, - template: html`
-
-
- {{ "reduce_text" | loc:$root.lang }}: - {{keyItems[selected[0]].label | locObj:$root.lang}} - (+{{ selected.length - 1 }}) - -
-
-
-
    -
  • - - {{keyItems['word'].label | locObj:$root.lang }} - Aa -
  • - {{'word_attr' | loc:$root.lang}} -
  • - - {{item.label | locObj:$root.lang }} -
  • - {{'sentence_attr' | loc:$root.lang}} -
  • - - {{item.label | locObj:$root.lang }} -
  • -
-
-
`, - - link(scope: ReduceSelectScope) { - scope.$watchCollection("items", function () { - if (!scope.items) return - scope.keyItems = _.keyBy(scope.items, "value") - scope.hasWordAttrs = scope.items.some((item) => item.group == "word_attr") - scope.hasStructAttrs = scope.items.some((item) => item.group == "sentence_attr") - - for (const name of scope.selected || []) { - if (name in scope.keyItems) scope.keyItems[name].selected = true - } - for (const name of scope.insensitive || []) { - if (name in scope.keyItems) scope.keyItems[name].insensitive = true - } - - // If no selection given, default to selecting the word option - const hasSelection = scope.items.some((item) => item.selected) - if (!hasSelection) { - scope.keyItems["word"].selected = true - } - updateSelected() - }) - - function updateSelected() { - // If unselecting the word option, reset the insensitive flag. - if (!scope.keyItems["word"].selected) { - scope.keyItems["word"].insensitive = false - } - - scope.selected = scope.items.filter((item) => item.selected).map((item) => item.value) - scope.insensitive = scope.items.filter((item) => item.insensitive).map((item) => item.value) - - $timeout(() => scope.onChange()) - } - - scope.toggleSelected = function (value, event) { - event.stopPropagation() - const item = scope.keyItems[value] - const isLinux = window.navigator.userAgent.indexOf("Linux") !== -1 - if (isLinux ? event.ctrlKey : event.altKey) { - // Unselect all options and select only the given option - scope.items.forEach((item) => (item.selected = false)) - item.selected = true - } else { - item.selected = !item.selected - } - updateSelected() - } - - scope.toggleWordInsensitive = function (event) { - event.stopPropagation() - scope.keyItems["word"].insensitive = !scope.keyItems["word"].insensitive - if (!scope.keyItems["word"].selected) { - scope.keyItems["word"].selected = true - } - updateSelected() - } - - scope.toggled = function (open) { - // if no element is selected when closing popop, select word - if (!open && !scope.selected.length) { - scope.keyItems["word"].selected = true - updateSelected() - } - } - }, - }), -]) diff --git a/app/styles/styles.scss b/app/styles/styles.scss index 7a88e28f2..433918299 100644 --- a/app/styles/styles.scss +++ b/app/styles/styles.scss @@ -1980,6 +1980,9 @@ li.active a span .num-to-find { background: rgb(242, 247, 255); } .attribute { + display: flex; + overflow-x: hidden; + white-space: nowrap; cursor: pointer; &:hover { background-color : lighten(#999, 30%); @@ -1991,7 +1994,7 @@ li.active a span .num-to-find { margin-right: 4px; } .reduce-label { - position: absolute; + flex-grow: 1; } ul { margin: 0;