From fb2e902a2c9b355ffe7a1de1927cf4463d51da15 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:42:13 +0100 Subject: [PATCH 01/10] add rejectSkipped --- client/src/utils/lastQueue.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/utils/lastQueue.ts b/client/src/utils/lastQueue.ts index 285ccad24638..f6166394acd2 100644 --- a/client/src/utils/lastQueue.ts +++ b/client/src/utils/lastQueue.ts @@ -5,6 +5,8 @@ type QueuedAction R, R = unknown> = { reject: (e: Error) => void; }; +export class ActionSkippedError extends Error {} + /** * This queue waits until the current promise is resolved and only executes the last enqueued * promise. Promises added between the last and the currently executing promise are skipped. @@ -13,15 +15,22 @@ type QueuedAction R, R = unknown> = { */ export class LastQueue R, R = unknown> { throttlePeriod: number; + /** Throw an error if a queued action is skipped. This avoids dangling promises */ + rejectSkipped: boolean; private queuedPromises: Record> = {}; private pendingPromise = false; - constructor(throttlePeriod = 1000) { + constructor(throttlePeriod = 1000, rejectSkipped = false) { this.throttlePeriod = throttlePeriod; + this.rejectSkipped = rejectSkipped; } async enqueue(action: T, arg: Parameters[0], key: string | number = 0) { return new Promise((resolve, reject) => { + if (this.rejectSkipped && this.queuedPromises[key]) { + this.queuedPromises[key]?.reject(new ActionSkippedError()); + } + this.queuedPromises[key] = { action, arg, resolve, reject }; this.dequeue(); }); From 387606722fac1e3e418b28277a69b610d88a0fce Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:47:01 +0100 Subject: [PATCH 02/10] remove dangling promises from historyItemsStore --- client/src/stores/historyItemsStore.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/stores/historyItemsStore.ts b/client/src/stores/historyItemsStore.ts index c996713c9cea..87f0012f918f 100644 --- a/client/src/stores/historyItemsStore.ts +++ b/client/src/stores/historyItemsStore.ts @@ -11,7 +11,7 @@ import Vue, { computed, ref } from "vue"; import type { DatasetSummary, HDCASummary } from "@/api"; import { HistoryFilters } from "@/components/History/HistoryFilters"; import { mergeArray } from "@/store/historyStore/model/utilities"; -import { LastQueue } from "@/utils/lastQueue"; +import { ActionSkippedError, LastQueue } from "@/utils/lastQueue"; import { urlData } from "@/utils/url"; export type HistoryItem = DatasetSummary | HDCASummary; @@ -19,7 +19,7 @@ export type HistoryItem = DatasetSummary | HDCASummary; const limit = 100; type ExpectedReturn = { stats: { total_matches: number }; contents: HistoryItem[] }; -const queue = new LastQueue(); +const queue = new LastQueue(1000, true); export const useHistoryItemsStore = defineStore("historyItemsStore", () => { const items = ref>({}); @@ -59,13 +59,19 @@ export const useHistoryItemsStore = defineStore("historyItemsStore", () => { const params = `v=dev&order=hid&offset=${offset}&limit=${limit}`; const url = `/api/histories/${historyId}/contents?${params}&${queryString}`; const headers = { accept: "application/vnd.galaxy.history.contents.stats+json" }; - return await queue.enqueue(urlData, { url, headers, errorSimplify: false }, historyId).then((data) => { + + try { + const data = await queue.enqueue(urlData, { url, headers, errorSimplify: false }, historyId); const stats = (data as ExpectedReturn).stats; totalMatchesCount.value = stats.total_matches; const payload = (data as ExpectedReturn).contents; const relatedHid = HistoryFilters.getFilterValue(filterText, "related"); saveHistoryItems(historyId, payload, relatedHid); - }); + } catch (e) { + if (!(e instanceof ActionSkippedError)) { + throw e; + } + } } function saveHistoryItems(historyId: string, payload: HistoryItem[], relatedHid = null) { From 0bae6bc523631dbcccb352560bb628b157825efb Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:59:04 +0100 Subject: [PATCH 03/10] remove vue import --- client/src/stores/historyItemsStore.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/stores/historyItemsStore.ts b/client/src/stores/historyItemsStore.ts index 87f0012f918f..5dcf46ee56df 100644 --- a/client/src/stores/historyItemsStore.ts +++ b/client/src/stores/historyItemsStore.ts @@ -6,7 +6,7 @@ import { reverse } from "lodash"; import { defineStore } from "pinia"; -import Vue, { computed, ref } from "vue"; +import { computed, ref, set } from "vue"; import type { DatasetSummary, HDCASummary } from "@/api"; import { HistoryFilters } from "@/components/History/HistoryFilters"; @@ -82,7 +82,7 @@ export const useHistoryItemsStore = defineStore("historyItemsStore", () => { payload.forEach((item: HistoryItem) => { // current `item.hid` is related to item with hid = `relatedHid` const relationKey = `${historyId}-${relatedHid}-${item.hid}`; - Vue.set(relatedItems.value, relationKey, true); + set(relatedItems.value, relationKey, true); }); } } From 21b6a83bcf5f5c72636efacfbec13ec37f034388 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:12:31 +0100 Subject: [PATCH 04/10] remove virtual scroller from listing layout --- .../History/CurrentHistory/HistoryPanel.vue | 21 ++- .../History/Layout/IntersectionObservable.vue | 27 +++ .../History/Layout/ListingLayout.vue | 160 ++++++++++-------- 3 files changed, 128 insertions(+), 80 deletions(-) create mode 100644 client/src/components/History/Layout/IntersectionObservable.vue diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index d0cd5257df7f..da28c3e45b6c 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -103,6 +103,10 @@ const historyItems = computed(() => { return historyItemsStore.getHistoryItems(props.history.id, filterText.value); }); +const visibleHistoryItems = computed(() => { + return historyItems.value.filter((item) => !invisibleHistoryItems.value[item.hid]); +}); + const formattedSearchError = computed(() => { const newError = unref(searchError); if (!newError) { @@ -368,7 +372,7 @@ onMounted(async () => { @query-selection-break="querySelectionBreak = true">
{ @hide="operationError = null" />
-
+
-
+
@@ -468,12 +472,12 @@ onMounted(async () => { + + diff --git a/client/src/components/History/Layout/IntersectionObservable.vue b/client/src/components/History/Layout/IntersectionObservable.vue new file mode 100644 index 000000000000..e63a97f4dabc --- /dev/null +++ b/client/src/components/History/Layout/IntersectionObservable.vue @@ -0,0 +1,27 @@ + + + diff --git a/client/src/components/History/Layout/ListingLayout.vue b/client/src/components/History/Layout/ListingLayout.vue index b0378191f113..2087bf8b120f 100644 --- a/client/src/components/History/Layout/ListingLayout.vue +++ b/client/src/components/History/Layout/ListingLayout.vue @@ -1,88 +1,98 @@ - - + + From 6525fd31f810355bdef01450342704c59b797ae4 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:16:11 +0100 Subject: [PATCH 05/10] remove unused css --- .../src/components/History/CurrentHistory/HistoryPanel.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index da28c3e45b6c..3d43b0cbea50 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -506,10 +506,3 @@ onMounted(async () => { - - From 980005f808ea3727f74f0622ebdfdfa035408669 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:39:19 +0100 Subject: [PATCH 06/10] add mock IntersectionObserver --- client/tests/jest/jest-environment.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/tests/jest/jest-environment.js b/client/tests/jest/jest-environment.js index cb044383bc8b..4664dbc122be 100644 --- a/client/tests/jest/jest-environment.js +++ b/client/tests/jest/jest-environment.js @@ -2,12 +2,21 @@ import "jsdom-worker"; import JSDOMEnvironment from "jest-environment-jsdom"; +class MockObserver { + constructor(...args) {} + + observe(...args) {} + unobserve(...args) {} +} + export default class CustomJSDOMEnvironment extends JSDOMEnvironment { constructor(...args) { super(...args); this.global.Worker = Worker; + this.global.IntersectionObserver = MockObserver; + // FIXME https://github.com/jsdom/jsdom/issues/3363 this.global.structuredClone = structuredClone; } From 63d9b2e4f0a7900b745aabcad965805bffe9eaa4 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:46:03 +0100 Subject: [PATCH 07/10] fix navigation selector --- client/src/utils/navigation/navigation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml index de60da96d863..043099be666d 100644 --- a/client/src/utils/navigation/navigation.yml +++ b/client/src/utils/navigation/navigation.yml @@ -265,7 +265,7 @@ history_panel: elements_warning: '.dataset-collection-panel .controls .elements-warning' tag_area_button: '.details .stateless-tags .toggle-button' tag_area_input: '.details .stateless-tags .headless-multiselect input' - list_items: '.dataset-collection-panel .listing .content-item' + list_items: '.dataset-collection-panel .listing-layout .content-item' back_to_history: svg[data-description="back to history"] selectors: From df7fa9272c592aabcf6f127fe6de0f38249b94ba Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:51:06 +0100 Subject: [PATCH 08/10] move skipPromise to method --- client/src/utils/lastQueue.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/utils/lastQueue.ts b/client/src/utils/lastQueue.ts index f6166394acd2..317d7dfa3a3c 100644 --- a/client/src/utils/lastQueue.ts +++ b/client/src/utils/lastQueue.ts @@ -25,12 +25,18 @@ export class LastQueue R, R = unknown> { this.rejectSkipped = rejectSkipped; } + private skipPromise(key: string | number) { + if (!this.rejectSkipped) { + return; + } + + const promise = this.queuedPromises[key]; + promise?.reject(new ActionSkippedError()); + } + async enqueue(action: T, arg: Parameters[0], key: string | number = 0) { return new Promise((resolve, reject) => { - if (this.rejectSkipped && this.queuedPromises[key]) { - this.queuedPromises[key]?.reject(new ActionSkippedError()); - } - + this.skipPromise(key); this.queuedPromises[key] = { action, arg, resolve, reject }; this.dequeue(); }); From f9d29ef8d05e85dc8c6fcbd8a91e640f2282efd0 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:04:05 +0100 Subject: [PATCH 09/10] browserslist ignore KaiOS --- client/package.json | 3 ++- client/src/components/History/Layout/ListingLayout.vue | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index 74a927ee93f8..f63ed194a64f 100644 --- a/client/package.json +++ b/client/package.json @@ -16,7 +16,8 @@ "browserslist": [ "defaults", "not op_mini all", - "not ios_saf <= 15.0" + "not ios_saf <= 15.0", + "not kaios > 0" ], "resolutions": { "chokidar": "3.5.3", diff --git a/client/src/components/History/Layout/ListingLayout.vue b/client/src/components/History/Layout/ListingLayout.vue index 2087bf8b120f..1c401ed3a880 100644 --- a/client/src/components/History/Layout/ListingLayout.vue +++ b/client/src/components/History/Layout/ListingLayout.vue @@ -49,7 +49,6 @@ function scrollToOffset(offset: number) { element?.scrollIntoView(); } -// eslint-disable-next-line compat/compat const observer = new IntersectionObserver( (items) => { for (let index = 0; index < items.length; index++) { From 9bdeb06e43b17579952768a087237c41330dd100 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:16:27 +0100 Subject: [PATCH 10/10] pass item index into current offset use lowest index as offset --- .../components/History/Layout/ListingLayout.vue | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/client/src/components/History/Layout/ListingLayout.vue b/client/src/components/History/Layout/ListingLayout.vue index 1c401ed3a880..c3525bef4455 100644 --- a/client/src/components/History/Layout/ListingLayout.vue +++ b/client/src/components/History/Layout/ListingLayout.vue @@ -5,7 +5,7 @@ import IntersectionObservable from "./IntersectionObservable.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; const props = defineProps<{ - items: unknown[]; + items: any[]; queryKey?: string; dataKey?: string; loading?: boolean; @@ -51,15 +51,9 @@ function scrollToOffset(offset: number) { const observer = new IntersectionObserver( (items) => { - for (let index = 0; index < items.length; index++) { - const element = items[index]!; - if (element.isIntersecting) { - const target = element.target as HTMLDivElement; - const targetIndex = parseInt(target.getAttribute("data-index") ?? "0"); - currentOffset.value = targetIndex; - break; - } - } + const intersecting = items.filter((item) => item.isIntersecting); + const indices = intersecting.map((item) => parseInt(item.target.getAttribute("data-index") ?? "0")); + currentOffset.value = indices.length > 0 ? Math.min(...indices) : 0; }, { root: root.value } ); @@ -81,7 +75,7 @@ function getKey(item: unknown, index: number) { class="listing-layout-item" :data-index="i" :observer="observer"> - +