@infinite-canvas-tutorial/webcomponents
Version:
WebComponents UI implementation
296 lines (294 loc) • 12 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextEditor = void 0;
const lit_1 = require("lit");
const decorators_js_1 = require("lit/decorators.js");
const context_1 = require("@lit/context");
const ecs_1 = require("@infinite-canvas-tutorial/ecs");
const uuid_1 = require("uuid");
const context_2 = require("../context");
let TextEditor = class TextEditor extends lit_1.LitElement {
constructor() {
super(...arguments);
this.binded = false;
this.handleBlur = (event) => {
const target = event.target;
const content = target.value;
if (content.trim() !== '' && content.trim() !== this.node.content) {
this.api.runAtNextTick(() => {
const entity = this.api.getEntity(this.node);
if (!entity) {
this.api.updateNode(Object.assign(Object.assign({}, this.node), { content, visibility: 'visible' }));
}
else {
this.api.updateNode(this.node, {
content,
visibility: 'visible',
});
}
this.api.record();
});
}
else {
this.api.runAtNextTick(() => {
this.api.updateNode(this.node, {
visibility: 'visible',
}, false);
});
}
const isPenSelect = this.appState.penbarSelected === ecs_1.Pen.SELECT;
if (!isPenSelect) {
this.api.setAppState({
penbarSelected: ecs_1.Pen.SELECT,
});
}
this.editable.style.display = 'none';
this.editable.value = '';
};
this.handleDblclick = (event) => {
const isPenSelect = this.appState.penbarSelected === ecs_1.Pen.SELECT;
const isPenText = this.appState.penbarSelected === ecs_1.Pen.TEXT;
if (!isPenSelect && !isPenText) {
return;
}
const { x: vx, y: vy } = this.api.client2Viewport({
x: event.clientX,
y: event.clientY,
});
const { x: wx, y: wy } = this.api.viewport2Canvas({
x: vx,
y: vy,
});
const entities = this.api.elementsFromBBox(wx, wy, wx, wy);
const entity = entities.find((e) => !e.has(ecs_1.UI));
this.node = undefined;
if (isPenSelect && entity && entity.has(ecs_1.Text)) {
// Edit the existing text node.
const node = this.api.getNodeByEntity(entity);
const { obb } = entity.read(ecs_1.ComputedBounds);
this.node = node;
this.editable.value = node.content;
// this.editable.style.color = node.fill;
// this.editable.style.opacity = node.opacity && node.opacity.toString();
// this.editable.style.textAlign = node.textAlign;
// TODO: support textBaseline.
// this.editable.style.textBaseline = node.textBaseline;
// this.editable.style.letterSpacing =
// node.letterSpacing && node.letterSpacing.toString();
this.updateTextareaStyle(node);
this.editable.style.width = `${obb.width}px`;
this.editable.style.height = `${obb.height}px`;
this.api.deselectNodes([node]);
this.api.unhighlightNodes([node]);
// Hide original text node for now.
this.api.updateNode(node, {
visibility: 'hidden',
}, false);
}
if (isPenText) {
// Create a new text node if blank area is clicked.
this.node = Object.assign({ id: (0, uuid_1.v4)(), type: 'text', content: '', anchorX: wx, anchorY: wy, zIndex: 0 }, this.appState.penbarText);
(0, ecs_1.inferXYWidthHeight)(this.node);
this.updateTextareaStyle(this.appState.penbarText);
}
if (!this.node) {
return;
}
this.updatePositionWithCamera();
this.editable.style.display = 'inline-block';
this.editable.style.transformOrigin = `left top`;
this.editable.focus();
};
this.handleKeyDown = (event) => {
// Prevent triggering arrow keys.
event.stopPropagation();
if (event.key === 'Escape') {
this.editable.blur();
// } else if (
// event.key === KEYS.TAB ||
// (event[KEYS.CTRL_OR_CMD] &&
// (event.code === CODES.BRACKET_LEFT ||
// event.code === CODES.BRACKET_RIGHT))
// ) {
// event.preventDefault();
// if (event.isComposing) {
// // input keyboard
// return;
// } else if (event.shiftKey || event.code === CODES.BRACKET_LEFT) {
// outdent();
// } else {
// indent();
// }
// // We must send an input event to resize the element
}
};
this.handleInput = (event) => {
const target = event.target;
const content = target.value;
const attributes = Object.assign(Object.assign({}, this.node), { content });
const { minX, minY, maxX, maxY } = ecs_1.Text.getGeometryBounds(attributes);
const width = maxX - minX;
const height = maxY - minY;
this.editable.style.width = `${width}px`;
this.editable.style.height = `${height}px`;
};
this.handleWheel = (event) => {
event.preventDefault();
event.stopPropagation();
const $canvas = this.api.getCanvasElement();
if ($canvas) {
const newWheelEvent = new WheelEvent('wheel', {
deltaX: event.deltaX,
deltaY: event.deltaY,
deltaZ: event.deltaZ,
deltaMode: event.deltaMode,
clientX: event.clientX,
clientY: event.clientY,
screenX: event.screenX,
screenY: event.screenY,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
bubbles: true,
cancelable: true,
});
$canvas.dispatchEvent(newWheelEvent);
}
};
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.api && this.api.getCanvas().has(ecs_1.Canvas)) {
const $canvas = this.api.getCanvasElement();
$canvas === null || $canvas === void 0 ? void 0 : $canvas.removeEventListener('dblclick', this.handleDblclick);
}
}
updatePositionWithCamera() {
if (this.node) {
const camera = this.api.getCamera();
const { zoom } = camera.read(ecs_1.ComputedCamera);
const { x, y } = this.api.canvas2Viewport({
x: this.node.x,
y: this.node.y,
});
this.editable.style.left = `${x}px`;
this.editable.style.top = `${y}px`;
this.editable.style.transform = `scale(${zoom})`;
}
}
updateTextareaStyle(node) {
const { fontFamily, fontSize, fontWeight, fontStyle, fontVariant, fill, opacity, textAlign, textBaseline, letterSpacing, } = node;
if (fontFamily) {
this.editable.style.fontFamily = fontFamily;
}
if (fontSize) {
this.editable.style.fontSize = `${fontSize}px`;
}
if (fontWeight) {
this.editable.style.fontWeight = fontWeight.toString();
}
if (fontStyle) {
this.editable.style.fontStyle = fontStyle;
}
if (fontVariant) {
this.editable.style.fontVariant = fontVariant;
}
if (fill) {
this.editable.style.color = fill;
}
if (opacity) {
this.editable.style.opacity = opacity.toString();
}
if (textAlign) {
this.editable.style.textAlign = textAlign;
}
if (textBaseline) {
// this.editable.style.textBaseline = textBaseline;
}
if (letterSpacing) {
this.editable.style.letterSpacing = letterSpacing.toString();
}
}
render() {
var _a;
// FIXME: wait for the element to be ready.
if (((_a = this.api) === null || _a === void 0 ? void 0 : _a.element) && !this.binded) {
const $canvas = this.api.getCanvasElement();
$canvas === null || $canvas === void 0 ? void 0 : $canvas.addEventListener('dblclick', this.handleDblclick);
this.binded = true;
}
if (this.prevCameraZoom !== this.appState.cameraZoom ||
this.prevCameraX !== this.appState.cameraX ||
this.prevCameraY !== this.appState.cameraY) {
this.updatePositionWithCamera();
this.prevCameraZoom = this.appState.cameraZoom;
this.prevCameraX = this.appState.cameraX;
this.prevCameraY = this.appState.cameraY;
}
return (0, lit_1.html) `<textarea
dir="auto"
tabindex="0"
wrap="off"
=${this.handleBlur}
=${this.handleInput}
=${this.handleKeyDown}
=${this.handleWheel}
></textarea>`;
}
};
exports.TextEditor = TextEditor;
// @see https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/wysiwyg/textWysiwyg.tsx#L309
TextEditor.styles = (0, lit_1.css) `
:host {
position: absolute;
}
textarea {
position: absolute;
display: none;
min-height: 1em;
backface-visibility: hidden;
margin: 0;
padding: 0;
border: 0;
outline: 0;
resize: none;
background: transparent;
overflow: hidden;
overflow-wrap: break-word;
box-sizing: content-box;
font-size: 16px;
font-family: system-ui;
font-weight: normal;
font-style: normal;
font-variant: normal;
letter-spacing: 0;
width: auto;
min-width: 1em;
}
textarea.wheel-transparent {
pointer-events: none;
}
`;
__decorate([
(0, context_1.consume)({ context: context_2.appStateContext, subscribe: true })
], TextEditor.prototype, "appState", void 0);
__decorate([
(0, context_1.consume)({ context: context_2.apiContext, subscribe: true })
], TextEditor.prototype, "api", void 0);
__decorate([
(0, decorators_js_1.query)('textarea')
], TextEditor.prototype, "editable", void 0);
__decorate([
(0, decorators_js_1.state)()
], TextEditor.prototype, "node", void 0);
exports.TextEditor = TextEditor = __decorate([
(0, decorators_js_1.customElement)('ic-spectrum-text-editor')
], TextEditor);
//# sourceMappingURL=text-editor.js.map