Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: annotation-highlighting on click #2474

Merged
merged 35 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
53bcdc5
Merge branch 'master' into feat/EC-6946-annotation-highlighting
lbiedinger Oct 28, 2024
919abe7
select annotations via canvas click; WIP
lbiedinger Oct 31, 2024
fbf1e0d
Merge branch 'master' into feat/EC-6946-annotation-highlighting
lbiedinger Nov 1, 2024
5d4c20b
merge master resolve conflicts
lbiedinger Nov 5, 2024
ae90b9a
restore polygon import
lbiedinger Nov 5, 2024
575fd3c
simplify anntation at coordinate lookup
lbiedinger Nov 11, 2024
1475241
Merge branch 'master' into feat/EC-6946-annotation-highlighting
lbiedinger Nov 11, 2024
eda1f35
refactor + specs
lbiedinger Nov 11, 2024
8990246
spec for annotation extent
lbiedinger Nov 11, 2024
80a5ff7
Merge branch 'master' into feat/EC-6946-annotation-highlighting
rwd Nov 12, 2024
76e6529
refactor: update route if active annotation changes
rwd Nov 12, 2024
3b7b581
do not .join annotation fetch calls, just construct array with null v…
lbiedinger Nov 12, 2024
935a788
highlight annotation on current page, not from list
lbiedinger Nov 12, 2024
43e9deb
on page switch fetch new canvas annotations
lbiedinger Nov 12, 2024
58be5fc
fix annotation detection for multiple list tabs; fix spec for setting…
lbiedinger Nov 13, 2024
7602848
refactor switching to annotations tab
lbiedinger Nov 13, 2024
6d4acfe
page change clears annotations; set annotation via route on canvas click
lbiedinger Nov 14, 2024
9ecf8ee
merge master resolve conflicts
lbiedinger Nov 14, 2024
4195f94
unit test
lbiedinger Nov 19, 2024
e784e3f
merge master; resolve conflicts
lbiedinger Nov 19, 2024
43a185c
merge master; resolve conflicts
lbiedinger Nov 20, 2024
16d96be
Merge branch 'master' into feat/EC-6946-annotation-highlighting
lbiedinger Nov 20, 2024
66d1179
select highlighted annotation on page change
lbiedinger Nov 20, 2024
9945f6c
watch fetchstate for activeAnnotation scroll
lbiedinger Nov 20, 2024
4acb5a1
Merge branch 'master' into feat/EC-6946-annotation-highlighting
rwd Nov 21, 2024
ea71118
refactor: scroll to annotation when set from route query
rwd Nov 21, 2024
130e860
refactor: reduce cognitive complexity of exclusion
rwd Nov 21, 2024
0c5af69
refactor: clean up code
rwd Nov 21, 2024
b5c74bc
Merge branch 'master' into feat/EC-6946-annotation-highlighting
rwd Nov 22, 2024
fbf598d
refactor: update pagination route on input blur/change
rwd Nov 22, 2024
e8bd0a5
fix: sidebar tabs not lazy
rwd Nov 22, 2024
1f6c271
refactor: annotation list re-fetches annotations
rwd Nov 22, 2024
83d74b2
Clean up unused activeAnnotation
LeoniePeters Nov 22, 2024
519ac88
refactor: use router replace for updating anno in url
rwd Nov 22, 2024
8f6c333
Bump size limit
LeoniePeters Nov 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions packages/portal/src/components/generic/PaginationNavInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@
>
<b-form-input
v-model="page"
name="page"
:aria-label="$t('pageNumber')"
:lazy="true"
:max="totalPages"
:min="1"
:number="true"
data-qa="pagination input"
type="number"
@blur.native="handlePageInputChange"
@change.native="handlePageInputChange"
/> {{ $t('of') }} {{ totalPages }}
</li>
<li
Expand Down Expand Up @@ -160,6 +163,13 @@
progress: {
type: Boolean,
default: false
},
/**
* Array of url params to exclude on pagination
*/
excludeParams: {
type: Array,
default: () => []
}
},

Expand Down Expand Up @@ -199,22 +209,25 @@
watch: {
'$route.query.page'() {
this.page = Number(this.$route?.query?.page) || 1;
},
page: 'changePaginationNav'
}
},

methods: {
changePaginationNav() {
if (this.page) {
this.$router.push(this.linkGen(this.page));
}
handlePageInputChange() {
this.page && this.$router.push(this.linkGen(this.page));
},

linkGen(pageNo) {
linkGen(page) {
const query = { ...this.$route.query, page };

for (const excludeParam of this.excludeParams) {
delete query[excludeParam];
}

return {
hash: this.$route.hash,
path: this.$route.path,
query: { ...this.$route.query, page: pageNo },
hash: this.$route.hash
query
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
:page-input="false"
:button-icon-class="'icon-arrow-outline'"
:progress="true"
:exclude-params="['anno']"
class="pagination ml-auto"
/>
<span class="divider" />
Expand Down Expand Up @@ -37,7 +38,7 @@
import PaginationNavInput from '@/components/generic/PaginationNavInput';

export default {
name: 'ItemMediaPaginationWidget',
name: 'ItemMediaPaginationToolbar',

components: {
PaginationNavInput
Expand Down
4 changes: 0 additions & 4 deletions packages/portal/src/components/item/ItemMediaPresentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
:height="resource.height"
:format="resource.format"
:service="resource.service"
:annotation="activeAnnotation"
:thumbnail="thumbnail"
@error="handleImageError"
>
Expand Down Expand Up @@ -81,7 +80,6 @@
v-else-if="resource?.edm.forEdmIsShownAt"
:url="resource.edm.preview.about"
:item-id="itemId"
:annotation="activeAnnotation"
:width="resource.edm.preview.ebucoreWidth"
:height="resource.edm.preview.ebucoreHeight"
:thumbnail="thumbnail"
Expand Down Expand Up @@ -188,7 +186,6 @@

setup() {
const {
activeAnnotation,
fetchPresentation,
hasAnnotations,
hasSearchService,
Expand All @@ -200,7 +197,6 @@
} = useItemMediaPresentation();

return {
activeAnnotation,
fetchPresentation,
hasAnnotations,
hasSearchService,
Expand Down
1 change: 0 additions & 1 deletion packages/portal/src/components/item/ItemMediaSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
v-if="annotationList"
data-qa="item media sidebar annotations"
button-id="item-media-sidebar-annotations"
lazy
:title-link-attributes="{ 'aria-label': $t('media.sidebar.annotations'), href: '#annotations' }"
>
<template #title>
Expand Down
2 changes: 1 addition & 1 deletion packages/portal/src/components/item/ItemMediaThumbnail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
link() {
return {
path: this.$route.path,
query: { ...this.$route.query, page: this.page },
query: { ...this.$route.query, page: this.page, anno: undefined },
hash: this.$route.hash
};
},
Expand Down
34 changes: 21 additions & 13 deletions packages/portal/src/components/media/MediaAnnotationList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@
if (!this.active) {
return;
}

await (this.searching ? this.searchAnnotations(`"${this.query}"`) : this.fetchCanvasAnnotations());

await Promise.all([
this.fetchCanvasAnnotations(),
this.searching ? this.searchAnnotations(`"${this.query}"`) : null
]);
this.setActiveAnnotationFromRouteQuery();

this.$emit('fetched', this.annotations.length);
Expand All @@ -134,12 +135,11 @@
this.scrollActiveAnnotationToCentre();
}
},
// TODO: should this watcher go into useItemMediaPresentation?
annotationUri() {
!this.searching && this.$fetch();
this.searching ? this.fetchCanvasAnnotations() : this.$fetch();
},
'$route.hash'() {
this.scrollActiveAnnotationToCentre();
this.scrollActiveAnnotationToCentre('instant');
},
'$route.query.anno'() {
this.setActiveAnnotationFromRouteQuery();
Expand All @@ -156,21 +156,22 @@
...this.$route,
query: {
...this.$route.query,
anno: anno.id,
page: this.pageForAnnotationTarget(anno.target)
anno: anno?.id,
page: this.pageForAnnotationTarget(anno?.target) || this.$route.query.page
}
};
},

async scrollActiveAnnotationToCentre(behavior = 'smooth') {
if (!this.active) {
if (!this.active || !this.activeAnnotation) {
return;
}
await this.$nextTick();

if (this.activeAnnotation && this.annotationScrollToContainerSelector && this.$refs.annotationListItems) {
const elementIndex = this.annotationList.findIndex((listItem) => listItem.id === this.activeAnnotation.id);
this.scrollElementToCentre(
this.$refs.annotationListItems[this.annotationList.indexOf(this.activeAnnotation)],
this.$refs.annotationListItems[elementIndex],
{
behavior,
container: document.querySelector(this.annotationScrollToContainerSelector)
Expand All @@ -180,10 +181,17 @@
},

setActiveAnnotationFromRouteQuery() {
if (this.$route.query.anno) {
this.setActiveAnnotation(this.annotationList.find((anno) => anno.id === this.$route.query.anno) || null);
process.client && this.scrollActiveAnnotationToCentre('instant');
if (!this.$route.query.anno) {
this.setActiveAnnotation(null);
return;
}
if (this.$route.query.anno !== this.activeAnnotation?.id) {
const activeAnnotation = this.annotations.find((anno) => anno.id === this.$route.query.anno) || this.annotationSearchResults.find((anno) => anno.id === this.$route.query.anno);
this.setActiveAnnotation(activeAnnotation || null);
}
this.$nextTick(() => {
process.client && this.scrollActiveAnnotationToCentre('instant');
});
}
}
};
Expand Down
62 changes: 42 additions & 20 deletions packages/portal/src/components/media/MediaImageViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import { easeOut } from 'ol/easing.js';
import { defaults } from 'ol/interaction/defaults';

import useItemMediaPresentation from '@/composables/itemMediaPresentation.js';
import useZoom from '@/composables/zoom.js';
import EuropeanaMediaAnnotation from '@/utils/europeana/media/Annotation.js';
import EuropeanaMediaService from '@/utils/europeana/media/Service.js';
Expand All @@ -51,11 +52,6 @@
},

props: {
// TODO: all we need is the target, not the full object
annotation: {
type: Object,
default: null
},
format: {
type: String,
default: null
Expand Down Expand Up @@ -94,8 +90,24 @@
setMax: setMaxZoom,
setMin: setMinZoom
} = useZoom();
const {
activeAnnotation,
annotationAtCoordinate,
hasAnnotations,
pageForAnnotationTarget
} = useItemMediaPresentation();

return { currentZoom, setCurrentZoom, setDefaultZoom, setMaxZoom, setMinZoom };
return {
activeAnnotation,
annotationAtCoordinate,
currentZoom,
hasAnnotations,
pageForAnnotationTarget,
setCurrentZoom,
setDefaultZoom,
setMaxZoom,
setMinZoom
};
},

data() {
Expand Down Expand Up @@ -130,7 +142,7 @@
},

watch: {
annotation: {
activeAnnotation: {
deep: true,
handler: 'highlightAnnotation'
},
Expand Down Expand Up @@ -162,15 +174,14 @@
},

constructAnnotationFeature() {
let annotation = this.annotation;
let annotation = this.activeAnnotation;
if (!annotation) {
return null;
} else if (!(annotation instanceof EuropeanaMediaAnnotation)) {
annotation = new EuropeanaMediaAnnotation(annotation);
}

// TODO: move to computed property `annotationXywh`? or onto EuropeanaMediaAnnotation class?
let [x, y, w, h] = this.olExtent;
const extent = annotation.extent || this.olExtent;
// FIXME: this.url will always be for the image, not the canvas, which works
// with europeana's incorrect annotation modelling, but not with
// others' correct modelling
Expand All @@ -180,15 +191,6 @@
return;
}

const targetHash = new URL(targetId).hash;
const xywhSelector = annotation.getHashParam(targetHash, 'xywh');
if (xywhSelector) {
[x, y, w, h] = xywhSelector
.split(',')
.map((xywh) => xywh.length === 0 ? undefined : Number(xywh));
}

const extent = [x, y, x + w, y + h];
const poly = fromExtent(extent);

// Vector Layer co-ordinates start bottom left, not top left, so transform
Expand All @@ -206,6 +208,21 @@
return new Feature(poly);
},

handleMapClick(coordinate) {
const clickedAnnotation = this.annotationAtCoordinate(coordinate, this.olExtent);
if ((clickedAnnotation?.id !== this.activeAnnotation?.id) || (this.$route.hash !== '#annotations')) {
this.$router.replace({
...this.$route,
hash: '#annotations',
query: {
...this.$route.query,
anno: clickedAnnotation?.id,
page: this.pageForAnnotationTarget(clickedAnnotation?.target) || this.$route.query.page
}
});
}
},

async highlightAnnotation() {
if (!this.fullImageRendered) {
await this.renderFullImage();
Expand Down Expand Up @@ -267,6 +284,11 @@

this.olMap.getView().fit(extent, { size: imageMaxFitSize });
this.configureZoomLevels();
if (this.hasAnnotations) {
this.olMap.on('click', (evt) => {
this.handleMapClick(evt.coordinate);
});
}
},

async renderThumbnail() {
Expand Down Expand Up @@ -352,7 +374,7 @@
if (this.source === 'IIIF') {
const mapOptions = this.initOlImageLayerIIIF();
this.initMapWithFullImage(mapOptions);
} else if (this.annotation) {
} else if (this.activeAnnotation) {
this.renderFullImage();
} else {
this.renderThumbnail();
Expand Down
12 changes: 11 additions & 1 deletion packages/portal/src/composables/itemMediaPresentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ const activeAnnotation = ref(null);
const page = ref(1);
const presentation = ref(null);

const annotationAtCoordinate = (coordinate, fullExtent) => {
const coordinateToCompare = [coordinate[0], fullExtent[3] - coordinate[1]];
rwd marked this conversation as resolved.
Show resolved Hide resolved
return annotations.value.find((anno) => {
return (anno.extent[0] <= coordinateToCompare[0]) &&
(anno.extent[2] >= coordinateToCompare[0]) &&
(anno.extent[1] <= coordinateToCompare[1]) &&
(anno.extent[3] >= coordinateToCompare[1]);
});
};

/**
* Annotation page/list: either a URI as a string, or an object with id
* property being the URI
Expand Down Expand Up @@ -119,7 +129,6 @@ const fetchCanvasAnnotations = async() => {
anno.body = anno.body[0];
}
}

annotations.value = annos;
};

Expand Down Expand Up @@ -157,6 +166,7 @@ const setPage = (value) => {
export default function useItemMediaPresentation() {
return {
annotations,
annotationAtCoordinate,
annotationCollection,
annotationSearchHits,
annotationSearchHitSelectorFor,
Expand Down
11 changes: 11 additions & 0 deletions packages/portal/src/utils/europeana/media/Annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ export default class EuropeanaMediaAnnotation extends Base {
parsed.body = Array.isArray(parsed.body) ? parsed.body.map((bod) => TextualBody.parse(bod)) : TextualBody.parse(parsed.body);
}

if (parsed.target) {
const targetHash = new URL(parsed.target?.id || parsed.target).hash;
const xywhSelector = this.getHashParam(targetHash, 'xywh');
if (xywhSelector) {
const extent = xywhSelector
.split(',')
.map((xywh) => xywh.length === 0 ? undefined : Number(xywh));
parsed.extent = [extent[0], extent[1], extent[0] + extent[2], extent[1] + extent[3]];
}
}

return parsed;
}

Expand Down
Loading
Loading