Skip to content

Commit

Permalink
New: Initial mobile optimization of annotation dialogs (#146)
Browse files Browse the repository at this point in the history
- Annotations will still remain view-only
  • Loading branch information
pramodsum authored May 26, 2017
1 parent e269605 commit d693991
Show file tree
Hide file tree
Showing 24 changed files with 763 additions and 193 deletions.
185 changes: 131 additions & 54 deletions src/lib/annotations/AnnotationDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as annotatorUtil from './annotatorUtil';
import * as constants from './annotationConstants';
import { CLASS_ACTIVE, CLASS_HIDDEN } from '../constants';
import { decodeKeydown } from '../util';
import { ICON_DELETE } from '../icons/icons';
import { ICON_CLOSE, ICON_DELETE } from '../icons/icons';

@autobind class AnnotationDialog extends EventEmitter {
//--------------------------------------------------------------------------
Expand Down Expand Up @@ -64,6 +64,25 @@ import { ICON_DELETE } from '../icons/icons';
* @return {void}
*/
show() {
// Populate mobile annotations dialog with annotations information
if (this.isMobile) {
this.element = document.querySelector(`.${constants.CLASS_MOBILE_ANNOTATION_DIALOG}`);
annotatorUtil.showElement(this.element);
this.element.appendChild(this.dialogEl);

if (this.highlightDialogEl && !this.hasComments) {
this.element.classList.add('bp-plain-highlight');

const headerEl = this.element.querySelector('.bp-annotation-mobile-header');
headerEl.classList.add(CLASS_HIDDEN);
}

const dialogCloseButtonEl = this.element.querySelector('.bp-annotation-dialog-close');
dialogCloseButtonEl.addEventListener('click', this.hideMobileDialog);

this.bindDOMListeners();
}

const textAreaEl = this.hasAnnotations
? this.element.querySelector(constants.SELECTOR_REPLY_TEXTAREA)
: this.element.querySelector(constants.SELECTOR_ANNOTATION_TEXTAREA);
Expand All @@ -76,7 +95,9 @@ import { ICON_DELETE } from '../icons/icons';

// Position and show - we need to reposition every time since the DOM
// could have changed from zooming
this.position();
if (!this.isMobile) {
this.position();
}

// Activate appropriate textarea
if (this.hasAnnotations) {
Expand All @@ -102,13 +123,39 @@ import { ICON_DELETE } from '../icons/icons';
}
}

/**
* Hides and resets the shared mobile dialog.
*
* @return {void}
*/
hideMobileDialog() {
if (!this.element) {
return;
}

// Clear annotations from dialog
this.element.innerHTML = `
<div class="bp-annotation-mobile-header">
<button class="bp-annotation-dialog-close">${ICON_CLOSE}</button>
</div>`.trim();
this.element.classList.remove('bp-plain-highlight');

const dialogCloseButtonEl = this.element.querySelector('.bp-annotation-dialog-close');
dialogCloseButtonEl.removeEventListener('click', this.hideMobileDialog);

annotatorUtil.hideElement(this.element);
}

/**
* Hides the dialog.
*
* @param {boolean} [noDelay] - Whether or not to have a timeout delay
* @return {void}
*/
hide() {
if (this.isMobile) {
this.hideMobileDialog();
}
annotatorUtil.hideElement(this.element);
this.deactivateReply();
}
Expand Down Expand Up @@ -171,52 +218,28 @@ import { ICON_DELETE } from '../icons/icons';
*/
setup(annotations) {
// Generate HTML of dialog
this.element = document.createElement('div');
this.element.setAttribute('data-type', 'annotation-dialog');
this.element.classList.add(constants.CLASS_ANNOTATION_DIALOG);
this.element.innerHTML = `
<div class="bp-annotation-caret"></div>
<div class="annotation-container">
<section class="${annotations.length ? CLASS_HIDDEN : ''}" data-section="create">
<textarea class="bp-textarea annotation-textarea"
placeholder="${__('annotation_add_comment_placeholder')}"></textarea>
<div class="button-container">
<button class="bp-btn cancel-annotation-btn" data-type="cancel-annotation-btn">
${__('annotation_cancel')}
</button>
<button class="bp-btn bp-btn-primary post-annotation-btn" data-type="post-annotation-btn">
${__('annotation_post')}
</button>
</div>
</section>
<section class="${annotations.length ? '' : CLASS_HIDDEN}" data-section="show">
<div class="annotation-comments"></div>
<div class="reply-container">
<textarea class="bp-textarea reply-textarea"
placeholder="${__('annotation_reply_placeholder')}" data-type="reply-textarea"></textarea>
<div class="button-container ${CLASS_HIDDEN}">
<button class="bp-btn cancel-annotation-btn" data-type="cancel-reply-btn">
${__('annotation_cancel')}
</button>
<button class="bp-btn bp-btn-primary post-annotation-btn" data-type="post-reply-btn">
${__('annotation_post')}
</button>
</div>
</div>
</section>
</section>`.trim();
this.dialogEl = this.generateDialogEl(annotations.length);
this.dialogEl.classList.add('annotation-container');

if (!this.isMobile) {
this.element = document.createElement('div');
this.element.setAttribute('data-type', 'annotation-dialog');
this.element.classList.add(constants.CLASS_ANNOTATION_DIALOG);
this.element.innerHTML = '<div class="bp-annotation-caret"></div>';
this.element.appendChild(this.dialogEl);

// Adding thread number to dialog
if (annotations.length > 0) {
this.element.dataset.threadNumber = annotations[0].thread;
}

// Adding thread number to dialog
if (annotations.length > 0) {
this.element.dataset.threadNumber = annotations[0].thread;
this.bindDOMListeners();
}

// Add annotation elements
annotations.forEach((annotation) => {
this.addAnnotationElement(annotation);
});

this.bindDOMListeners();
}

/**
Expand All @@ -229,9 +252,12 @@ import { ICON_DELETE } from '../icons/icons';
this.element.addEventListener('keydown', this.keydownHandler);
this.element.addEventListener('click', this.clickHandler);
this.element.addEventListener('mouseup', this.stopPropagation);
this.element.addEventListener('mouseenter', this.mouseenterHandler);
this.element.addEventListener('mouseleave', this.mouseleaveHandler);
this.element.addEventListener('wheel', this.stopPropagation);

if (!this.isMobile) {
this.element.addEventListener('mouseenter', this.mouseenterHandler);
this.element.addEventListener('mouseleave', this.mouseleaveHandler);
}
}

/**
Expand All @@ -244,9 +270,12 @@ import { ICON_DELETE } from '../icons/icons';
this.element.removeEventListener('keydown', this.keydownHandler);
this.element.removeEventListener('click', this.clickHandler);
this.element.removeEventListener('mouseup', this.stopPropagation);
this.element.removeEventListener('mouseenter', this.mouseenterHandler);
this.element.removeEventListener('mouseleave', this.mouseleaveHandler);
this.element.removeEventListener('wheel', this.stopPropagation);

if (!this.isMobile) {
this.element.removeEventListener('mouseenter', this.mouseenterHandler);
this.element.removeEventListener('mouseleave', this.mouseleaveHandler);
}
}

/**
Expand Down Expand Up @@ -429,7 +458,7 @@ import { ICON_DELETE } from '../icons/icons';
</div>
</div>`.trim();

const annotationContainerEl = this.element.querySelector(constants.SELECTOR_COMMENTS_CONTAINER);
const annotationContainerEl = this.dialogEl.querySelector(constants.SELECTOR_COMMENTS_CONTAINER);
annotationContainerEl.appendChild(annotationEl);
}

Expand Down Expand Up @@ -467,7 +496,11 @@ import { ICON_DELETE } from '../icons/icons';
* @return {void}
*/
activateReply() {
const replyTextEl = this.element.querySelector(constants.SELECTOR_REPLY_TEXTAREA);
if (!this.dialogEl) {
return;
}

const replyTextEl = this.dialogEl.querySelector(constants.SELECTOR_REPLY_TEXTAREA);

// Don't activate if reply textarea is already active
const isActive = replyTextEl.classList.contains(CLASS_ACTIVE);
Expand All @@ -480,8 +513,10 @@ import { ICON_DELETE } from '../icons/icons';
annotatorUtil.showElement(replyButtonEls);

// Auto scroll annotations dialog to bottom where new comment was added
const annotationsEl = this.element.querySelector('.annotation-container');
annotationsEl.scrollTop = annotationsEl.scrollHeight - annotationsEl.clientHeight;
const annotationsEl = this.dialogEl.querySelector('.annotation-container');
if (annotationsEl) {
annotationsEl.scrollTop = annotationsEl.scrollHeight - annotationsEl.clientHeight;
}
}

/**
Expand All @@ -492,12 +527,12 @@ import { ICON_DELETE } from '../icons/icons';
* @return {void}
*/
deactivateReply(clearText) {
if (!this.element) {
if (!this.dialogEl) {
return;
}

const replyTextEl = this.element.querySelector(constants.SELECTOR_REPLY_TEXTAREA);
const replyButtonEls = replyTextEl.parentNode.querySelector(constants.SELECTOR_BUTTON_CONTAINER);
const replyTextEl = this.dialogEl.querySelector(constants.SELECTOR_REPLY_TEXTAREA);
const replyButtonEls = this.dialogEl.querySelector(constants.SELECTOR_BUTTON_CONTAINER);
annotatorUtil.resetTextarea(replyTextEl, clearText);
annotatorUtil.hideElement(replyButtonEls);

Expand All @@ -506,8 +541,10 @@ import { ICON_DELETE } from '../icons/icons';
}

// Auto scroll annotations dialog to bottom where new comment was added
const annotationsEl = this.element.querySelector('.annotation-container');
annotationsEl.scrollTop = annotationsEl.scrollHeight - annotationsEl.clientHeight;
const annotationsEl = this.dialogEl.querySelector('.annotation-container');
if (annotationsEl) {
annotationsEl.scrollTop = annotationsEl.scrollHeight - annotationsEl.clientHeight;
}
}

/**
Expand Down Expand Up @@ -570,6 +607,46 @@ import { ICON_DELETE } from '../icons/icons';
deleteAnnotation(annotationID) {
this.emit('annotationdelete', { annotationID });
}

/**
* Generates the annotation dialog DOM element
*
* @private
* @param {number} numAnnotations - length of annotations array
* @return {HTMLElement} Annotation dialog DOM element
*/
generateDialogEl(numAnnotations) {
const dialogEl = document.createElement('div');
dialogEl.innerHTML = `
<section class="${numAnnotations ? CLASS_HIDDEN : ''}" data-section="create">
<textarea class="bp-textarea annotation-textarea"
placeholder="${__('annotation_add_comment_placeholder')}"></textarea>
<div class="button-container">
<button class="bp-btn cancel-annotation-btn" data-type="cancel-annotation-btn">
${__('annotation_cancel')}
</button>
<button class="bp-btn bp-btn-primary post-annotation-btn" data-type="post-annotation-btn">
${__('annotation_post')}
</button>
</div>
</section>
<section class="${numAnnotations ? '' : CLASS_HIDDEN}" data-section="show">
<div class="annotation-comments"></div>
<div class="reply-container">
<textarea class="bp-textarea reply-textarea"
placeholder="${__('annotation_reply_placeholder')}" data-type="reply-textarea"></textarea>
<div class="button-container ${CLASS_HIDDEN}">
<button class="bp-btn cancel-annotation-btn" data-type="cancel-reply-btn">
${__('annotation_cancel')}
</button>
<button class="bp-btn bp-btn-primary post-annotation-btn" data-type="post-reply-btn">
${__('annotation_post')}
</button>
</div>
</div>
</section>`.trim();
return dialogEl;
}
}

export default AnnotationDialog;
15 changes: 13 additions & 2 deletions src/lib/annotations/AnnotationThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { ICON_PLACED_ANNOTATION } from '../icons/icons';
this.thread = data.thread || '';
this.type = data.type;
this.locale = data.locale;
this.isMobile = data.isMobile;

this.setup();
}
Expand Down Expand Up @@ -284,6 +285,10 @@ import { ICON_PLACED_ANNOTATION } from '../icons/icons';
this.createDialog();
this.bindCustomListenersOnDialog();

if (this.dialog) {
this.dialog.isMobile = this.isMobile;
}

this.setupElement();
}

Expand Down Expand Up @@ -311,7 +316,10 @@ import { ICON_PLACED_ANNOTATION } from '../icons/icons';

this.element.addEventListener('click', this.showDialog);
this.element.addEventListener('mouseenter', this.showDialog);
this.element.addEventListener('mouseleave', this.mouseoutHandler);

if (!this.isMobile) {
this.element.addEventListener('mouseleave', this.mouseoutHandler);
}
}

/**
Expand All @@ -327,7 +335,10 @@ import { ICON_PLACED_ANNOTATION } from '../icons/icons';

this.element.removeEventListener('click', this.showDialog);
this.element.removeEventListener('mouseenter', this.showDialog);
this.element.removeEventListener('mouseleave', this.mouseoutHandler);

if (!this.isMobile) {
this.element.removeEventListener('mouseleave', this.mouseoutHandler);
}
}

/**
Expand Down
Loading

0 comments on commit d693991

Please sign in to comment.