Reimplementation of text editing

This commit is contained in:
Piotr Pekala 2018-12-12 06:55:22 -08:00
parent 92d6c8e411
commit f0582e6a9e
5 changed files with 86 additions and 66 deletions

View File

@ -9,4 +9,5 @@ export class DrawingsEventSource {
public resized = new EventEmitter<ResizedDataEvent<MapDrawing>>(); public resized = new EventEmitter<ResizedDataEvent<MapDrawing>>();
public textAdded = new EventEmitter<TextAddedDataEvent>(); public textAdded = new EventEmitter<TextAddedDataEvent>();
public textEdited = new EventEmitter<TextEditedDataEvent>(); public textEdited = new EventEmitter<TextEditedDataEvent>();
public textSaved = new EventEmitter<any>();
} }

View File

@ -1,15 +1,19 @@
import { Injectable, EventEmitter } from "@angular/core"; import { Injectable, EventEmitter } from "@angular/core";
import { TextAddedDataEvent } from '../events/event-source'; import { TextAddedDataEvent } from '../events/event-source';
import { DrawingsEventSource } from '../events/drawings-event-source';
@Injectable() @Injectable()
export class TextAddingTool { export class TextAddingTool {
private enabled;
private listener: Function; private listener: Function;
private temporaryElement: HTMLDivElement;
public addingFinished = new EventEmitter<any>(); public addingFinished = new EventEmitter<any>();
constructor(
private drawingEventSource: DrawingsEventSource
){}
public setEnabled(enabled){ public setEnabled(enabled){
this.enabled = enabled;
if (enabled){ if (enabled){
this.activate(); this.activate();
} else { } else {
@ -26,37 +30,43 @@ export class TextAddingTool {
var map = document.getElementsByClassName('map')[0]; var map = document.getElementsByClassName('map')[0];
let addTextListener = (event: MouseEvent) => { let addTextListener = (event: MouseEvent) => {
this.temporaryElement = this.getTemporaryElement(event.clientX, event.clientY);
var div = document.createElement('div'); document.body.appendChild(this.temporaryElement);
div.style.width = "fit-content"; this.temporaryElement.focus();
div.style.left = event.clientX.toString() + 'px';
div.style.top = (event.clientY).toString() + 'px';
div.style.position = "absolute";
div.style.zIndex = "99";
div.style.fontFamily = "Noto Sans";
div.style.fontSize = "11pt";
div.style.fontWeight = "bold";
div.style.color = "#000000";
div.setAttribute("contenteditable", "true");
document.body.appendChild(div);
div.innerText = "";
div.focus();
document.body.style.cursor = "text"; document.body.style.cursor = "text";
div.addEventListener("focusout", () => { this.temporaryElement.addEventListener("focusout", () => {
let savedText = div.innerText; let savedText = this.temporaryElement.innerText;
this.addingFinished.emit(new TextAddedDataEvent(savedText, event.clientX, event.clientY)); this.addingFinished.emit(new TextAddedDataEvent(savedText, event.clientX, event.clientY));
document.body.style.cursor = "default"; this.drawingEventSource.textSaved.subscribe((evt:boolean) => {
div.remove(); if(evt){
this.temporaryElement.remove();
document.body.style.cursor = "default";
}
});
}); });
} }
map.removeEventListener('click', this.listener as EventListenerOrEventListenerObject); map.removeEventListener('click', this.listener as EventListenerOrEventListenerObject);
this.listener = addTextListener; this.listener = addTextListener;
map.addEventListener('click', this.listener as EventListenerOrEventListenerObject, {once : true}); map.addEventListener('click', this.listener as EventListenerOrEventListenerObject, {once : true});
} }
private getTemporaryElement(x:number, y:number): HTMLDivElement{
var elem = document.createElement('div');
elem.style.width = "fit-content";
elem.style.left = x.toString() + 'px';
elem.style.top = y.toString() + 'px';
elem.style.position = "absolute";
elem.style.zIndex = "99";
elem.style.fontFamily = "Noto Sans";
elem.style.fontSize = "11pt";
elem.style.fontWeight = "bold";
elem.style.color = "#000000";
elem.setAttribute("contenteditable", "true");
elem.innerText = "";
return elem;
}
} }

View File

@ -3,6 +3,7 @@ import { SVGSelection } from '../models/types';
import { select } from 'd3-selection'; import { select } from 'd3-selection';
import { TextElement } from '../models/drawings/text-element'; import { TextElement } from '../models/drawings/text-element';
import { TextEditedDataEvent } from '../events/event-source'; import { TextEditedDataEvent } from '../events/event-source';
import { DrawingsEventSource } from '../events/drawings-event-source';
@Injectable() @Injectable()
@ -10,8 +11,13 @@ export class TextEditingTool {
private enabled = true; private enabled = true;
private editingDrawingId: string; private editingDrawingId: string;
private editedElement: any; private editedElement: any;
private temporaryElement: HTMLDivElement;
public editingFinished = new EventEmitter<any>(); public editingFinished = new EventEmitter<any>();
constructor(
private drawingEventSource: DrawingsEventSource
){}
public setEnabled(enabled) { public setEnabled(enabled) {
this.enabled = enabled; this.enabled = enabled;
} }
@ -34,45 +40,44 @@ export class TextEditingTool {
this.editingDrawingId = textElements[index].parentElement.parentElement.getAttribute("drawing_id"); this.editingDrawingId = textElements[index].parentElement.parentElement.getAttribute("drawing_id");
select(textElements[index].parentElement.parentElement.parentElement) var splitted = textElements[index].parentElement.getAttribute("transform").split(/\(|\)/);
.append("foreignObject") var x = Number(splitted[1].split(/,/)[0]);
.attr("width", '1000px') var y = Number(splitted[1].split(/,/)[1]);
.attr("min-width", 'fit-content') console.log(x);
.attr("height", '100px') console.log(y);
.attr("id", "temporaryText")
.attr("transform", textElements[index].parentElement.getAttribute("transform"))
.append("xhtml:span")
.attr("width", "fit-content")
.attr("height", "fit-content")
.attr("class", "temporaryTextInside")
.attr('style', () => {
const styles: string[] = [];
styles.push(`white-space: pre-line`)
styles.push(`outline: 0px solid transparent`)
styles.push(`font-family: ${elem.font_family}`)
styles.push(`font-size: ${elem.font_size}pt!important`);
styles.push(`font-weight: ${elem.font_weight}`)
styles.push(`color: ${elem.fill}`);
return styles.join("; ");
})
.attr('text-decoration', elem.text_decoration)
.attr('contenteditable', 'true')
.text(elem.text)
.on("focusout", () => {
let temporaryText = document.getElementsByClassName("temporaryTextInside")[0] as HTMLElement;
let savedText = temporaryText.innerText;
this.editingFinished.emit(new TextEditedDataEvent(this.editingDrawingId, savedText, this.editedElement));
var temporaryElement = document.getElementById("temporaryText") as HTMLElement; this.temporaryElement = document.createElement('div');
temporaryElement.remove(); this.temporaryElement.style.width = "fit-content";
this.temporaryElement.style.left = '100px';//x.toString() + 'px';
this.temporaryElement.style.top = '100px';//y.toString() + 'px';
this.temporaryElement.style.position = "absolute";
this.temporaryElement.style.zIndex = "99";
this.temporaryElement.style.fontFamily = elem.font_family;
this.temporaryElement.style.fontSize = `${elem.font_size}pt`;
this.temporaryElement.style.fontWeight = elem.font_weight;
this.temporaryElement.style.color = elem.fill;
this.temporaryElement.style.textDecoration = elem.text_decoration;
this.temporaryElement.setAttribute("contenteditable", "true");
this.temporaryElement.innerText = elem.text;
selection.selectAll<SVGTextElement, TextElement>('text.editingMode') this.temporaryElement.addEventListener("focusout", () => {
.attr("visibility", "visible") let savedText = this.temporaryElement.innerText;
.classed("editingMode", false); this.editingFinished.emit(new TextEditedDataEvent(this.editingDrawingId, savedText, this.editedElement));
this.drawingEventSource.textSaved.subscribe((evt:boolean) => {
if(evt){
this.temporaryElement.remove();
document.body.style.cursor = "default";
}
}); });
var txtInside = document.getElementsByClassName("temporaryTextInside")[0] as HTMLElement; selection.selectAll<SVGTextElement, TextElement>('text.editingMode')
txtInside.focus(); .attr("visibility", "visible")
.classed("editingMode", false);
});
document.body.appendChild(this.temporaryElement);
this.temporaryElement.focus();
}); });
} }
} }

View File

@ -356,7 +356,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
}); });
} }
public onTextAdded(evt: TextAddedDataEvent){ public onTextAdded(evt: TextAddedDataEvent) {
this.resetDrawToolChoice(); this.resetDrawToolChoice();
let drawing = this.getDrawingMock("text", evt.savedText); let drawing = this.getDrawingMock("text", evt.savedText);
@ -368,10 +368,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
.subscribe((serverDrawing: Drawing) => { .subscribe((serverDrawing: Drawing) => {
document.body.style.cursor = "default"; document.body.style.cursor = "default";
this.drawingsDataSource.add(serverDrawing); this.drawingsDataSource.add(serverDrawing);
this.drawingsEventSource.textSaved.emit(true);
}); });
} }
public onTextEdited(evt: TextEditedDataEvent){ public onTextEdited(evt: TextEditedDataEvent) {
let mapDrawing: MapDrawing = new MapDrawing(); let mapDrawing: MapDrawing = new MapDrawing();
mapDrawing.element = evt.textElement; mapDrawing.element = evt.textElement;
(mapDrawing.element as TextElement).text = evt.editedText; (mapDrawing.element as TextElement).text = evt.editedText;
@ -383,6 +384,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
.updateText(this.server, drawing, svgString) .updateText(this.server, drawing, svgString)
.subscribe((serverDrawing: Drawing) => { .subscribe((serverDrawing: Drawing) => {
this.drawingsDataSource.update(serverDrawing); this.drawingsDataSource.update(serverDrawing);
this.drawingsEventSource.textSaved.emit(true);
}); });
} }
@ -465,7 +467,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
map.addEventListener('click', this.drawListener as EventListenerOrEventListenerObject, {once : true}); map.addEventListener('click', this.drawListener as EventListenerOrEventListenerObject, {once : true});
} }
public resetDrawToolChoice(){ public resetDrawToolChoice() {
this.drawTools.isRectangleChosen = false; this.drawTools.isRectangleChosen = false;
this.drawTools.isEllipseChosen = false; this.drawTools.isEllipseChosen = false;
this.drawTools.isLineChosen = false; this.drawTools.isLineChosen = false;
@ -473,14 +475,14 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.selectedDrawing = ""; this.selectedDrawing = "";
} }
public hideMenu(){ public hideMenu() {
var map = document.getElementsByClassName('map')[0]; var map = document.getElementsByClassName('map')[0];
map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject); map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
this.resetDrawToolChoice(); this.resetDrawToolChoice();
this.drawTools.visibility = false; this.drawTools.visibility = false;
} }
public showMenu(){ public showMenu() {
setTimeout(() => { setTimeout(() => {
this.drawTools.visibility = true; this.drawTools.visibility = true;
}, },
@ -546,10 +548,12 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
return mapDrawing; return mapDrawing;
} }
public addText(){ public addText() {
if (!this.drawTools.isAddingTextChosen){ if (!this.drawTools.isAddingTextChosen){
this.resetDrawToolChoice(); this.resetDrawToolChoice();
this.drawTools.isAddingTextChosen = true; this.drawTools.isAddingTextChosen = true;
var map = document.getElementsByClassName('map')[0];
map.removeEventListener('click', this.drawListener as EventListenerOrEventListenerObject);
} else { } else {
this.resetDrawToolChoice(); this.resetDrawToolChoice();
} }