184 lines
5.5 KiB
JavaScript
Executable File
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 */
|