Skip to content

Commit

Permalink
feat: enable tooltip on each annotation hover - DISABLED due to uncle…
Browse files Browse the repository at this point in the history
…ar reqs
  • Loading branch information
paulpestov committed Mar 27, 2023
1 parent e119421 commit c64acd1
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 222 deletions.
1 change: 0 additions & 1 deletion examples/ahiqar-arabic-karshuni.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="initial-scale=1,maximum-scale=5,minimum-scale=1,width=device-width">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="dist/tido.css">
<link rel="stylesheet" href="config-menu/config-menu.css">
<style>
Expand Down
1 change: 0 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">

<link rel="icon" type="image/x-icon" href="favicon.ico">
<style>
html, body {
margin: 0;
Expand Down
8 changes: 8 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
5 changes: 4 additions & 1 deletion src/components/Content.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ export default {
const root = document.getElementById('text-content');
this.$store.dispatch('annotations/addHighlightAttributesToText', root);
await this.$store.dispatch('annotations/addHighlightClickListeners');
await this.$store.dispatch('annotations/addHighlightHoverListeners');
// TODO: Enable Hover + Tooltip feature when reqs are clarified
// await this.$store.dispatch('annotations/addHighlightHoverListeners');
this.$store.commit('contents/setActiveContentUrl', this.url);
}, 100);
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion src/css/_global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ h3 {
}

.col {
flex: 1 1 0%;
flex: 1 1 0;
}

.q-card--dark {
Expand Down
40 changes: 40 additions & 0 deletions src/css/_tooltip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#annotation-tooltip {
background-color: white;
box-shadow: $shadow-3;
font-size: 14px;
left: 0;
opacity: 0;
padding: 8px;
position: fixed;
text-decoration: none;
top: 0;
transition: opacity 0.4s;
max-width: 480px;
z-index: 10000;
transform: translateY(-100%);
border-radius: $generic-border-radius;
border: 1px solid #ddd;
-webkit-touch-callout: none;

.body--dark & {
background-color: $dark;
border: 1px solid #424242;
}

&.annotation-animated-tooltip {
opacity: 1;
}

.referenced-annotation {
display: block;
margin-bottom: 4px;
}

.referenced-annotation:last-of-type {
margin-bottom: 0;
}

.tooltip-body {
border-top: 1px solid #ddd;
}
}
40 changes: 40 additions & 0 deletions src/css/_tooltip.scss~Stashed changes
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#annotation-tooltip {
background-color: white;
box-shadow: $shadow-3;
font-size: 14px;
left: 0;
opacity: 0;
padding: 8px;
position: fixed;
text-decoration: none;
top: 0;
transition: opacity 0.4s;
max-width: 480px;
z-index: 10000;
transform: translateY(-100%);
border-radius: $generic-border-radius;
border: 1px solid #ddd;
-webkit-touch-callout: none;

.body--dark & {
background-color: $dark;
border: 1px solid #424242;
}

&.annotation-animated-tooltip {
opacity: 1;
}

.referenced-annotation {
display: block;
margin-bottom: 4px;
}

.referenced-annotation:last-of-type {
margin-bottom: 0;
}

.tooltip-body {
border-top: 1px solid #ddd;
}
}
1 change: 1 addition & 0 deletions src/css/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
@import 'global.scss';
@import 'scrollbar.scss';
@import 'highlighting';
@import 'tooltip';
1 change: 1 addition & 0 deletions src/i18n/de/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,5 @@ export default {
other: 'Sonstiges',
subtitle: 'Untertitel',
manifest: 'Manifest',
more_annotations: 'Weitere'
};
1 change: 1 addition & 0 deletions src/i18n/en/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ export default {
other: 'Other',
subtitle: 'Subtitle',
manifest: 'Manifest',
also_selected: 'Also selected'
};
173 changes: 48 additions & 125 deletions src/store/annotations/actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as AnnotationUtils from '@/utils/annotations';
import { request } from '@/utils/http';
import * as Utils from '@/utils';
import { hasParentAnnotation } from "@/utils/annotations";

export const addActiveAnnotation = ({ getters, rootGetters, dispatch }, id) => {
const { activeAnnotations, annotations } = getters;
Expand Down Expand Up @@ -65,15 +66,8 @@ export const setFilteredAnnotations = ({ commit, getters, rootGetters }, types)
};

export const addHighlightAttributesToText = ({ getters }, dom) => {
console.log('addhigh')
const { annotations } = getters;

// Add range attributes
// [...dom.querySelectorAll('[data-target]:not([value=""])')]
// .map((el) => el.getAttribute('data-target').replace('_start', '').replace('_end', ''))
// .forEach((targetSelector) => Utils.addRangeHighlightAttributes(targetSelector, dom));

// Add single attributes
annotations.forEach((annotation) => {
const { id } = annotation;
const selector = Utils.generateTargetSelector(annotation);
Expand Down Expand Up @@ -147,122 +141,56 @@ export const addHighlightHoverListeners = ({ getters, rootGetters }) => {

const annotationElements = Array.from(document.querySelectorAll('[data-annotation]'));

annotationElements
// Annotations can be nested, so we filter out all outer elements from this selection and
// iterate over the deepest elements
.filter(el => [...el.childNodes].filter(childNode => childNode.nodeName === '#text').length > 0)
.forEach(deepestEl => {
console.log(deepestEl)
deepestEl.addEventListener(
// Annotations can be nested, so we filter out all outer elements from this selection and
// iterate over the deepest elements
annotationElements.forEach(el => {
el.addEventListener(
'mouseenter',
() => {
console.log('hover')
// Hovering is only supported for selected annotations
if (!AnnotationUtils.isAnnotationSelected(deepestEl)) return;
({clientX: x, clientY: y}) => {
let elementFromPoint = document.elementFromPoint(x, y);

if (!elementFromPoint.hasAttribute('data-annotation')) {
elementFromPoint = null;
}

const currentElement = elementFromPoint ?? el;

const { filteredAnnotations } = getters;
const annotationTooltipModels = filteredAnnotations.reduce((acc, curr) => {
const { id } = curr;
const name = rootGetters['config/getIconByType'](curr.body['x-content-type']);

acc[id] = {
value: curr.body.value,
name,
};
return acc;
}, {});

let current = deepestEl;

const annotationIds = [current.getAttribute('data-annotation-ids')];

while (current.parentElement.getAttribute('data-annotation')) {
annotationIds.push(current.getAttribute('data-annotation-ids'));
current = current.parentElement;
}
const currentAnnotations = Utils.getValuesFromAttribute(currentElement, 'data-annotation-ids');
const closestAnnotationId = currentAnnotations[currentAnnotations.length - 1];
const closestAnnotationTooltipModel = annotationTooltipModels[closestAnnotationId]
let annotationIds = discoverParentAnnotationIds(currentElement);
annotationIds = discoverChildAnnotationIds(currentElement, annotationIds);

// checks for duplicate class names.
const currentAnnotationTooltipModels = annotationIds
// Flatten
.join(' ').split(' ')
.map(id => {
return annotationTooltipModels[id]
})
const otherAnnotationTooltipModels = Object.keys(annotationIds)
.map(id => annotationTooltipModels[id])
.filter(m => m);

AnnotationUtils.createTooltip.bind(
AnnotationUtils.createOrUpdateTooltip.bind(
this,
current,
currentAnnotationTooltipModels,
document.getElementById('text-content')
currentElement,
{ closest: closestAnnotationTooltipModel, other: otherAnnotationTooltipModels },
document.getElementById('text-content'),
)();
},
false,
);
deepestEl.addEventListener(
el.addEventListener(
'mouseout',
() => document.querySelectorAll('.annotation-tooltip2').forEach((el) => el.remove()),
() => document.querySelectorAll('#annotation-tooltip').forEach((el) => el.remove()),
false,
);
});

return;


const { filteredAnnotations } = getters;
const annotationIds = filteredAnnotations.reduce((acc, curr) => {
const { id } = curr;
const name = rootGetters['config/getIconByType'](curr.body['x-content-type']);

acc[AnnotationUtils.stripAnnotationId(id)] = {
value: curr.body.value,
name,
};
return acc;
}, {});

document.querySelectorAll('[data-annotation]')
.forEach((el) => {
const hasChildNodes = [...el.childNodes].filter((x) => x.nodeName !== '#text').length > 0;

if (!hasChildNodes) {
const classNames = [];
el = AnnotationUtils.getHighestParentAnnotationElement(el);
const annotationClasses = [];

// checks for duplicate class names.
classNames
.join(' ')
.split(' ')
.map((x) => annotationIds[x])
.filter((x) => x)
.reduce((acc, curr) => {
if (!acc[curr.value]) {
acc[curr.value] = true;
annotationClasses.push(curr);
}
return acc;
}, {});

if (annotationClasses.length) {
el.addEventListener(
'mouseenter',
() => {
if (AnnotationUtils.isAnnotationSelected(el)) {
console.log('sel')
AnnotationUtils.createTooltip.bind(this, el, annotationClasses)();
}
},
false,
);
el.addEventListener(
'mouseout',
() => document.querySelectorAll('.annotation-tooltip').forEach((el) => el.remove()),
false,
);
}
}
});
};

export const addHighlightClickListeners = ({ dispatch, getters }) => {
Expand Down Expand Up @@ -291,7 +219,7 @@ export const addHighlightClickListeners = ({ dispatch, getters }) => {
// Next we look up which annotations need to be selected
let annotationIds = {};

getValuesFromAttribute(target, 'data-annotation-ids').forEach((value) => annotationIds[value] = true);
Utils.getValuesFromAttribute(target, 'data-annotation-ids').forEach((value) => annotationIds[value] = true);
annotationIds = discoverParentAnnotationIds(target, annotationIds);
annotationIds = discoverChildAnnotationIds(target, annotationIds);

Expand Down Expand Up @@ -325,32 +253,6 @@ export const addHighlightClickListeners = ({ dispatch, getters }) => {
}
return getNearestParentAnnotation(parent);
}

function getValuesFromAttribute(element, attribute) {
const value = element.getAttribute(attribute);
return value ? value.split(' ') : [];
}

function discoverParentAnnotationIds(el, annotationIds = {}) {
const parent = el.parentElement;
if (parent && parent.id !== 'text-content') {
getValuesFromAttribute(parent, 'data-annotation-ids').forEach((value) => annotationIds[value] = true);
return discoverParentAnnotationIds(parent, annotationIds);
}
return annotationIds;
}

function discoverChildAnnotationIds(el, annotationIds = {}) {
const { children } = el;

[...children].forEach((child) => {
if (child.dataset.annotation) {
getValuesFromAttribute(child, 'data-annotation-ids').forEach((value) => annotationIds[value] = true);
annotationIds = discoverChildAnnotationIds(child, annotationIds);
}
});
return annotationIds;
}
};

export const selectAll = ({ getters, dispatch }) => {
Expand All @@ -362,3 +264,24 @@ export const selectNone = ({ getters, dispatch }) => {
const { filteredAnnotations, activeAnnotations } = getters;
filteredAnnotations.forEach(({ id }) => activeAnnotations[id] && dispatch('removeActiveAnnotation', id));
};

function discoverParentAnnotationIds(el, annotationIds = {}) {
const parent = el.parentElement;
if (parent && parent.id !== 'text-content') {
Utils.getValuesFromAttribute(parent, 'data-annotation-ids').forEach((value) => annotationIds[value] = true);
return discoverParentAnnotationIds(parent, annotationIds);
}
return annotationIds;
}

function discoverChildAnnotationIds(el, annotationIds = {}) {
const { children } = el;

[...children].forEach((child) => {
if (child.dataset.annotation) {
Utils.getValuesFromAttribute(child, 'data-annotation-ids').forEach((value) => annotationIds[value] = true);
annotationIds = discoverChildAnnotationIds(child, annotationIds);
}
});
return annotationIds;
}
Loading

0 comments on commit c64acd1

Please sign in to comment.