Files
slava.home/main_plugin/dgrm/ui/shape-menu.js

184 lines
5.5 KiB
JavaScript
Executable File

import { CanvasSmbl } from '../infrastructure/canvas-smbl.js';
import { pointInCanvas } from '../infrastructure/move-scale-applay.js';
import { listen } from '../infrastructure/util.js';
import { tipShow } from './ui.js';
export class ShapeMenu extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML =
`<style>
.menu {
position: absolute !important;
overflow-x: auto;
padding: 0;
top: 50%;
left: 13px;
transform: translateY(-50%);
box-shadow: 0px 0px 58px 2px rgba(34, 60, 80, 0.2);
border-radius: 16px;
background-color: rgba(255,255,255, .9);
}
.content {
white-space: nowrap;
display: flex;
flex-direction: column;
}
[data-cmd] {
cursor: pointer;
}
.menu svg { padding: 10px; }
.stroke {
stroke: #344767;
stroke-width: 2px;
fill: transparent;
}
.menu .big {
width: 62px;
min-width: 62px;
}
@media only screen and (max-width: 700px) {
.menu {
width: 100%;
border-radius: 0;
bottom: 0;
display: flex;
flex-direction: column;
top: unset;
left: unset;
transform: unset;
}
.content {
align-self: center;
flex-direction: row;
}
}
</style>
<div id="menu" class="menu" style="touch-action: none;">
<div class="content">
<svg class="stroke" data-cmd="shapeAdd" data-cmd-arg="1" viewBox="0 0 24 24" width="24" height="24"><circle r="9" cx="12" cy="12"></circle></svg>
<svg class="stroke" data-cmd="shapeAdd" data-cmd-arg="4" viewBox="0 0 24 24" width="24" height="24"><path d="M2 12 L12 2 L22 12 L12 22 Z" stroke-linejoin="round"></path></svg>
<svg class="stroke" data-cmd="shapeAdd" data-cmd-arg="2" viewBox="0 0 24 24" width="24" height="24"><rect x="2" y="4" width="20" height="16" rx="3" ry="3"></rect></svg>
<svg data-cmd="shapeAdd" data-cmd-arg="0" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 8v8a3 3 0 0 1-3 3H7.83a3.001 3.001 0 1 1 0-2H10a1 1 0 0 0 1-1V8a3 3 0 0 1 3-3h3V2l5 4-5 4V7h-3a1 1 0 0 0-1 1z" fill="rgba(52,71,103,1)"/></svg>
<svg data-cmd="shapeAdd" data-cmd-arg="3" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M13 6v15h-2V6H5V4h14v2z" fill="rgba(52,71,103,1)"/></svg>
</div>
</div>`;
const menu = shadow.getElementById('menu');
menu.querySelectorAll('[data-cmd="shapeAdd"]').forEach(el => listen(el, 'pointerdown', this));
listen(menu, 'pointerleave', this);
listen(menu, 'pointerup', this);
listen(menu, 'pointermove', this);
};
/** @param {CanvasElement} canvas */
init(canvas) {
/** @private */ this._canvas = canvas;
}
/** @param {PointerEvent & { currentTarget: Element }} evt */
handleEvent(evt) {
switch (evt.type) {
case 'pointermove':
if (!this._isNativePointerleaveTriggered) {
// emulate pointerleave for mobile
const pointElem = document.elementFromPoint(evt.clientX, evt.clientY);
if (pointElem === this._pointElem) {
return;
}
// pointerleave
if (this._parentElem === this._pointElem) {
// TODO: check mobile
this._canvas.ownerSVGElement.setPointerCapture(evt.pointerId);
}
/**
* @type {Element}
* @private
*/
this._pointElem = pointElem;
}
break;
case 'pointerleave':
this._isNativePointerleaveTriggered = true;
if (this._pressedShapeTemplKey != null) {
// when shape drag out from menu panel
this._shapeCreate(evt);
}
this._clean();
break;
case 'pointerdown':
this._pressedShapeTemplKey = parseInt(evt.currentTarget.getAttribute('data-cmd-arg'));
// for emulate pointerleave
this._parentElem = document.elementFromPoint(evt.clientX, evt.clientY);
this._pointElem = this._parentElem;
this._isNativePointerleaveTriggered = null;
break;
case 'pointerup':
this._clean();
break;
}
}
/**
* @param {PointerEvent} evt
* @private
*/
_shapeCreate(evt) {
tipShow(false);
if (this._pressedShapeTemplKey == null) return;
const svg = this._canvas.ownerSVGElement;
const pt = svg.createSVGPoint();
pt.x = evt.clientX; pt.y = evt.clientY;
const loc = pt.matrixTransform(this._canvas.getScreenCTM().inverse());
const x = loc.x, y = loc.y;
let shapeData;
if (this._pressedShapeTemplKey === 0) {
shapeData = {
s: { data: { dir: 'right', position: { x: x - 24, y } } },
e: { data: { dir: 'right', position: { x: x + 24, y } } }
};
} else {
shapeData = {
type: this._pressedShapeTemplKey,
position: { x, y },
title: 'Title'
};
}
const shapeEl = this._canvas[CanvasSmbl].shapeMap[this._pressedShapeTemplKey].create(shapeData);
this._canvas.append(shapeEl);
// Для стрелки и других форм диспатчим pointerdown
shapeEl.dispatchEvent(new PointerEvent('pointerdown', evt));
// transform ставим только для фигур, которые не path
if (this._pressedShapeTemplKey !== 0) {
shapeEl.setAttribute('transform', `translate(${x},${y})`);
}
}
/** @private */
_clean() {
this._pressedShapeTemplKey = null;
this._parentElem = null;
this._pointElem = null;
}
}
customElements.define('ap-menu-shape', ShapeMenu);
/** @typedef { import('../shapes/shape-type-map.js').ShapeType } ShapeType */
/** @typedef { import('../infrastructure/canvas-smbl.js').CanvasElement } CanvasElement */