You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/ui-default/components/marker/Marker.js

194 lines
5.3 KiB
JavaScript

import $ from 'jquery';
import _ from 'lodash';
import Tooltip from 'vj/components/tooltip/Tooltip';
import { delay, i18n } from 'vj/utils';
const MARKER_ID = `marker_${Math.floor(Math.random() * 0xFFFFFF).toString(16)}`;
const MARKER_OFFSET = 20;
const MARKER_MAX_DISTANCE = 60;
let markerInstance = null;
function distanceToRect(px, py, rect) {
const cx = (rect.left + rect.right) / 2;
const cy = (rect.top + rect.bottom) / 2;
const dx = Math.max(Math.abs(px - cx) - (rect.right - rect.left) / 2, 0);
const dy = Math.max(Math.abs(py - cy) - (rect.bottom - rect.top) / 2, 0);
return Math.sqrt(dx * dx + dy * dy);
}
class Marker {
static exists() {
return markerInstance && document.getElementById(MARKER_ID);
}
constructor() {
// eslint-disable-next-line no-constructor-return
if (Marker.exists()) return markerInstance;
if (markerInstance) {
markerInstance.destroy();
}
this.$dom = $(`
<div class="marker" id="${MARKER_ID}"><div class="marker__toolbar">
<div class="marker__action" data-color="#ffff00" data-tooltip="${i18n('Mark Yellow')}"><span class="marker__icon icon-yellow"></span></div>
<div class="marker__action" data-color="#47ff6f" data-tooltip="${i18n('Mark Green')}"><span class="marker__icon icon-green"></span></div>
<div class="marker__action" data-color="#ff3b30" data-tooltip="${i18n('Mark Red')}"><span class="marker__icon icon-red"></span></div>
<div class="marker__action" data-color="transparent" data-tooltip="${i18n('Clear Marks')}"><span class="icon icon-erase"></span></div>
</div></div>
`)
.appendTo('body');
this.$dom.find('.marker__toolbar').on('click', '.marker__action', this.onMarkerActionClick.bind(this));
this.$dom.on('mousedown', this.onMarkerMouseDown.bind(this));
this.isOpen = false;
this.bindedHandlers = false;
this._onKeyDown = this.onKeyDown.bind(this);
this._onScroll = _.throttle(this.onScroll.bind(this), 50);
this._onMouseDown = this.onMouseDown.bind(this);
this._onMouseMove = _.throttle(this.onMouseMove.bind(this), 50);
markerInstance = this;
}
markSelection(color) {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
this.$container.attr('contentEditable', true);
if (range) {
// recover ranges
selection.removeAllRanges();
selection.addRange(range);
}
if (!document.execCommand('HiliteColor', false, color)) {
document.execCommand('BackColor', false, color);
}
this.$container.removeAttr('contentEditable');
if (range) {
// remove ranges after marking
selection.removeAllRanges();
}
}
showAtPosition($container, x, y) {
this.$container = $container;
this.$dom.css({
display: 'block',
opacity: 0,
});
const rect = this.$dom[0].getBoundingClientRect();
this.$dom.css({
left: x + MARKER_OFFSET,
top: y - (rect.bottom - rect.top) - MARKER_OFFSET,
});
this.isOpen = true;
this.$dom.addClass('open');
this.updateMarkerOpacity(x, y);
this.bindEventHandlersForClosing();
}
async close() {
if (!this.isOpen) {
return;
}
this.isOpen = false;
this.closeTooltipsImmediately();
this.unbindEventHandlers();
this.$dom
.css({ opacity: 0 })
.removeClass('open');
await delay(200);
if (!this.isOpen) {
this.$dom.hide();
}
}
closeTooltipsImmediately() {
const $elements = this.$dom.find('[data-tooltip]');
$elements.get().forEach((element) => {
const $el = $(element);
const tooltipInstance = Tooltip.get($el);
if (tooltipInstance) {
tooltipInstance.detach();
}
});
}
bindEventHandlersForClosing() {
if (this.bindedHandlers) {
return;
}
$(document).on('keydown', this._onKeyDown);
$(window).on('scroll', this._onScroll);
$(document).on('mousedown', this._onMouseDown);
$(document).on('mousemove', this._onMouseMove);
this.bindedHandlers = true;
}
unbindEventHandlers() {
if (!this.bindedHandlers) {
return;
}
$(document).off('keydown', this._onKeyDown);
$(window).off('scroll', this._onScroll);
$(document).off('mousedown', this._onMouseDown);
$(document).off('mousemove', this._onMouseMove);
this.bindedHandlers = false;
}
onMarkerActionClick(ev) {
const color = $(ev.currentTarget).attr('data-color');
this.markSelection(color);
this.close();
}
onMarkerMouseDown(ev) {
ev.stopPropagation();
ev.preventDefault();
}
onKeyDown() {
this.close();
}
onScroll() {
this.close();
}
onMouseMove(ev) {
this.updateMarkerOpacity(ev.clientX, ev.clientY);
}
onMouseDown() {
this.close();
}
updateMarkerOpacity(x, y) {
const markerRect = this.$dom[0].getBoundingClientRect();
const distance = distanceToRect(x, y, markerRect);
if (distance > MARKER_MAX_DISTANCE) {
this.close();
return;
}
this.$dom.css('opacity', 1 - (distance / MARKER_MAX_DISTANCE));
}
destroy() {
this.close();
this.$dom.remove();
markerInstance = null;
}
}
const MarkerInterface = {
close() {
if (!markerInstance) {
return;
}
markerInstance.close();
},
showAtPosition($container, x, y) {
const marker = new Marker();
marker.showAtPosition($container, x, y);
},
};
export default MarkerInterface;