Skip to content

Commit

Permalink
feat: annotation list markup (#2462)
Browse files Browse the repository at this point in the history
* feat: annotation list markup

* Styles annotations list

* Annotations search form attributes and styles

* Fix tooltips and Links tab

* Fix unit tests

* Remove left and right border annotation list items

* Remove sidebar border-bottom

* Add annotations count

* Override tooltip left margin

* Set annotations count when fetched

* Fix search input margin and border corners

---------

Co-authored-by: Leonie Peters <[email protected]>
  • Loading branch information
rwd and LeoniePeters authored Nov 1, 2024
1 parent 7dc3bae commit e88405a
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 100 deletions.
81 changes: 57 additions & 24 deletions packages/portal/src/components/item/ItemMediaSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,72 @@
name="fade"
>
<div
class="iiif-viewer-sidebar border-bottom"
class="iiif-viewer-sidebar"
data-qa="item media sidebar"
>
<b-tabs
id="item-media-sidebar"
v-model="activeTabIndex"
vertical
>
<!-- Place tooltip outside tab to prevent being lazy loaded -->
<b-tooltip
v-if="annotationList"
target="item-media-sidebar-annotations"
:title="$t('media.sidebar.annotations')"
boundary=".iiif-viewer-sidebar"
placement="right"
custom-class="ml-0"
/>
<b-tab
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' }"
@mouseleave.native="hideTooltips"
>
<b-tooltip
target="item-media-sidebar-annotations"
:title="$t('media.sidebar.annotations')"
boundary=".iiif-viewer-sidebar"
/>
<template #title>
<span class="icon icon-annotations" />
<!-- Listen to mouseleave on span, on b-tab does not work -->
<span
class="icon icon-annotations"
@mouseleave="hideTooltips"
/>
</template>
<h2
class="px-3"
data-qa="item media sidebar annotations title"
>
{{ $t('media.sidebar.annotations') }}
{{ $tc('media.sidebar.annotationsCount', annotationsCount) }}
</h2>
<MediaAnnotationList
v-if="activeTabHistory.includes('#annotations')"
:active="activeTabHash === '#annotations'"
class="iiif-viewer-sidebar-panel"
@fetched="handleAnnotationsFetched"
/>
</b-tab>
<b-tooltip
v-if="annotationSearch"
target="item-media-sidebar-search"
:title="$t('media.sidebar.search')"
boundary=".iiif-viewer-sidebar"
placement="right"
custom-class="ml-0"
/>
<b-tab
v-if="annotationSearch"
data-qa="item media sidebar search"
button-id="item-media-sidebar-search"
:title-link-attributes="{ 'aria-label': $t('media.sidebar.search'), href: '#search' }"
@mouseleave.native="hideTooltips"
>
<b-tooltip
target="item-media-sidebar-search"
:title="$t('media.sidebar.search')"
boundary=".iiif-viewer-sidebar"
/>
<template #title>
<span
class="icon icon-search-in-text"
@mouseleave="hideTooltips"
/>
</template>
<h2
id="item-media-sidebar-search-title"
class="px-3"
>
{{ $t('media.sidebar.search') }}
Expand All @@ -67,29 +80,37 @@
class="iiif-viewer-sidebar-panel"
/>
</b-tab>
<b-tooltip
v-if="!!manifestUri"
target="item-media-sidebar-links"
:title="$t('media.sidebar.links')"
boundary=".iiif-viewer-sidebar"
placement="right"
custom-class="ml-0"
/>
<b-tab
v-if="!!manifestUri"
data-qa="item media sidebar links"
button-id="item-media-sidebar-links"
lazy
:title-link-attributes="{ 'aria-label': $t('media.sidebar.links'), href: '#links' }"
@mouseleave.native="hideTooltips"
>
<b-tooltip
target="item-media-sidebar-links"
:title="$t('media.sidebar.links')"
boundary=".iiif-viewer-sidebar"
/>
<template #title>
<span
class="icon icon-link"
@mouseleave="hideTooltips"
/>
</template>
<h2>{{ $t('media.sidebar.links') }}</h2>
<h3>{{ $t('media.sidebar.IIIFManifest') }}</h3>
<h2 class="px-3">
{{ $t('media.sidebar.links') }}
</h2>
<h3 class="px-3">
{{ $t('media.sidebar.IIIFManifest') }}
</h3>
<b-link
:href="manifestUri"
target="_blank"
class="manifest-link d-inline-block px-3"
>
{{ manifestUri }}
</b-link>
Expand Down Expand Up @@ -150,6 +171,18 @@
const { activeTabHash, activeTabHistory, activeTabIndex } = useActiveTab(tabHashes);
return { activeTabHash, activeTabHistory, activeTabIndex };
},
data() {
return {
annotationsCount: null
};
},
methods: {
handleAnnotationsFetched(annotationsLength) {
this.annotationsCount = annotationsLength;
}
}
};
</script>
Expand Down Expand Up @@ -188,7 +221,7 @@
margin-bottom: 0.5rem;
}
a {
.manifest-link {
overflow-wrap: anywhere;
color: $blue;
font-size: $font-size-small;
Expand Down
121 changes: 78 additions & 43 deletions packages/portal/src/components/media/MediaAnnotationList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,43 @@
</b-col>
</b-row>
</b-container>
<!-- TODO: consider what the best markup is for these annotations, for UX and a11y -->
<b-list-group
<ol
v-else
class="iiif-annotation-list"
class="iiif-annotation-list list-group"
>
<b-list-group-item
<li
v-for="(anno, index) in annotationList"
:key="index"
ref="annotationListItems"
:action="true"
:active="anno.id === activeAnnotation?.id"
:lang="anno.body.language"
@click="handleClickListItem(anno)"
class="list-group-item list-group-item-action"
:class="{ active: anno.id === activeAnnotation?.id }"
data-qa="annotation list item"
>
<template
v-if="searching"
<!--
use replace, not push, so that the back button will leave the page,
and e.g. go back to search results instead of through myriad
previously selected annotations
-->
<NuxtLink
:to="annotationLinkRoute(anno)"
:replace="true"
>
{{ annotationSearchHitSelectorFor(anno.id).prefix }}<!--
--><strong class="has-text-highlight">{{ annotationSearchHitSelectorFor(anno.id).exact }}</strong><!--
-->{{ annotationSearchHitSelectorFor(anno.id).suffix }}
</template>
<template
v-else
>
{{ anno.body.value }}
</template>
</b-list-group-item>
</b-list-group>
<template
v-if="searching"
>
{{ annotationSearchHitSelectorFor(anno.id).prefix }}<!--
--><strong class="has-text-highlight">{{ annotationSearchHitSelectorFor(anno.id).exact }}</strong><!--
-->{{ annotationSearchHitSelectorFor(anno.id).suffix }}
</template>
<template
v-else
>
{{ anno.body.value }}
</template>
</NuxtLink>
</li>
</ol>
</div>
</template>
Expand Down Expand Up @@ -103,10 +112,9 @@
await (this.searching ? this.searchAnnotations(`"${this.query}"`) : this.fetchCanvasAnnotations());
if (this.$route.query.anno) {
this.setActiveAnnotation(this.annotationList.find((anno) => anno.id === this.$route.query.anno) || null);
process.client && this.scrollActiveAnnotationToCentre('instant');
}
this.setActiveAnnotationFromRouteQuery();
this.$emit('fetched', this.annotations.length);
},
computed: {
Expand All @@ -133,15 +141,25 @@
'$route.hash'() {
this.scrollActiveAnnotationToCentre();
},
'$route.query.anno'() {
this.setActiveAnnotationFromRouteQuery();
},
query() {
this.searching && this.$fetch();
}
},
methods: {
handleClickListItem(anno) {
this.setActiveAnnotation(anno);
this.updateAnnoRoute();
// TODO: md5 the anno param to prevent the url getting too long?
annotationLinkRoute(anno) {
return {
...this.$route,
query: {
...this.$route.query,
anno: anno.id,
page: this.pageForAnnotationTarget(anno.target)
}
};
},
async scrollActiveAnnotationToCentre(behavior = 'smooth') {
Expand All @@ -161,33 +179,50 @@
}
},
updateAnnoRoute() {
let page = null;
let anno = null;
if (this.activeAnnotation) {
// store the annotation id in the route, to pre-highlight it on page reload
// TODO: md5 this to prevent the url getting too long?
anno = this.activeAnnotation.id;
page = this.pageForAnnotationTarget(this.activeAnnotation.target);
setActiveAnnotationFromRouteQuery() {
if (this.$route.query.anno) {
this.setActiveAnnotation(this.annotationList.find((anno) => anno.id === this.$route.query.anno) || null);
process.client && this.scrollActiveAnnotationToCentre('instant');
}
// use replace, not push, so that the back button will leave the page,
// and e.g. go back to search results instead of through myriad
// previously selected annotations
this.$router.replace({ ...this.$route, query: { ...this.$route.query, anno, page } });
}
}
};
</script>
<style lang="scss" scoped>
<style lang="scss">
@import '@europeana/style/scss/variables';
.iiif-annotation-list {
background-color: $white;
.list-group-item-action {
cursor: pointer;
.list-group-item {
border-radius: 0;
border: none;
border-top: 1px solid $middlegrey;
&:last-child {
border-bottom: 1px solid $middlegrey;
}
&:hover {
background-color: transparent;
}
&.active {
background-color: transparent;
border: 1px solid $blue;
}
a {
color: $mediumgrey;
text-decoration: none;
font-size: $font-size-small;
&:hover {
color: $blue;
}
}
}
}
</style>
34 changes: 32 additions & 2 deletions packages/portal/src/components/media/MediaAnnotationSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@
<div>
<b-form
id="media-annotation-search-form"
class="px-3"
class="search-form position-relative"
inline
role="search"
@submit.prevent="handleSubmitForm"
>
<b-form-group>
<b-form-input
id="media-annotation-search-query"
v-model="query"
:placeholder="$t('media.sidebar.searchPlaceholder')"
name="query"
type="text"
type="search"
aria-labelledby="item-media-sidebar-search-title"
class="form-control"
/>
</b-form-group>
</b-form>
Expand Down Expand Up @@ -59,3 +64,28 @@
}
};
</script>

<style lang="scss" scoped>
@import '@europeana/style/scss/variables';
.search-form {
width: auto;
margin: 0 0.75rem 1.5rem;
padding-left: 2rem;
&::before {
top: 0.75rem;
left: 0.75rem;
}
&.form-inline .form-group {
flex-shrink: 1;
margin-bottom: 0;
}
.form-control {
padding: 0.75rem;
border-radius: 0.5rem
}
}
</style>
Loading

0 comments on commit e88405a

Please sign in to comment.