@itwin/core-markup
Version:
iTwin.js markup package
200 lines • 10.2 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module MarkupTools
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EditTextTool = exports.PlaceTextTool = void 0;
const core_frontend_1 = require("@itwin/core-frontend");
const svg_js_1 = require("@svgdotjs/svg.js");
const Markup_1 = require("./Markup");
const MarkupTool_1 = require("./MarkupTool");
const RedlineTool_1 = require("./RedlineTool");
// cspell:ignore rbox
/** Tool to place new text notes on a Markup.
* @public
*/
class PlaceTextTool extends RedlineTool_1.RedlineTool {
static toolId = "Markup.Text.Place";
static iconSpec = "icon-text-medium";
_nRequiredPoints = 1;
_minPoints = 0;
_value;
async onPostInstall() {
this._value = Markup_1.MarkupApp.props.text.startValue; // so applications can put a default string (e.g. user's initials) in the note. Can be empty
return super.onPostInstall();
}
showPrompt() { this.provideToolAssistance(`${MarkupTool_1.MarkupTool.toolKey}Text.Place.Prompts.FirstPoint`, true); }
async createMarkup(svg, ev, isDynamics) {
if (isDynamics && core_frontend_1.InputSource.Touch === ev.inputSource)
return;
const start = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // starting point in viewbox coordinates
const text = new svg_js_1.Text().plain(this._value); // create a plain text element
svg.put(text); // add it to the supplied container
this.setCurrentTextStyle(text); // apply active text style
text.translate(start.x, start.y); // and position it relative to the cursor
if (isDynamics) {
svg.add(text.getOutline().attr(Markup_1.MarkupApp.props.text.edit.textBox).addClass(Markup_1.MarkupApp.textOutlineClass)); // in dynamics, draw the box around the text
}
else {
await new EditTextTool(text, true).run(); // text is now positioned, open text editor
}
}
async onResetButtonUp(_ev) {
await this.exitTool();
return core_frontend_1.EventHandled.Yes;
}
}
exports.PlaceTextTool = PlaceTextTool;
/** Tool for editing text. Started automatically by the place text tool and by clicking on text from the SelectTool
* @public
*/
class EditTextTool extends MarkupTool_1.MarkupTool {
text;
_fromPlaceTool;
static toolId = "Markup.Text.Edit";
static iconSpec = "icon-text-medium";
editor;
editDiv;
boxed;
constructor(text, _fromPlaceTool = false) {
super();
this.text = text;
this._fromPlaceTool = _fromPlaceTool;
}
showPrompt() {
const mainInstruction = core_frontend_1.ToolAssistance.createInstruction(this.iconSpec, core_frontend_1.IModelApp.localization.getLocalizedString(`${MarkupTool_1.MarkupTool.toolKey}Text.Edit.Prompts.FirstPoint`));
const mouseInstructions = [];
const touchInstructions = [];
const acceptMsg = core_frontend_1.CoreTools.translate("ElementSet.Inputs.Accept");
const rejectMsg = core_frontend_1.CoreTools.translate("ElementSet.Inputs.Exit");
touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.OneTouchTap, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Touch));
mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.LeftClick, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.TwoTouchTap, rejectMsg, false, core_frontend_1.ToolAssistanceInputMethod.Touch));
mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.RightClick, rejectMsg, false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
const sections = [];
sections.push(core_frontend_1.ToolAssistance.createSection(mouseInstructions, core_frontend_1.ToolAssistance.inputsLabel));
sections.push(core_frontend_1.ToolAssistance.createSection(touchInstructions, core_frontend_1.ToolAssistance.inputsLabel));
const instructions = core_frontend_1.ToolAssistance.createInstructions(mainInstruction, sections);
core_frontend_1.IModelApp.notifications.setToolAssistance(instructions);
}
/** Open the text editor */
startEditor() {
let text = this.text;
if (text === undefined)
return;
if (text instanceof svg_js_1.G) {
this.boxed = text;
text = text.children()[1];
if (!(text instanceof svg_js_1.Text))
return;
this.text = text;
}
const markupDiv = this.markup.markupDiv;
const editDiv = this.editDiv = document.createElement("div"); // create a new DIV to hold the text editor
const editProps = Markup_1.MarkupApp.props.text.edit;
let style = editDiv.style;
style.backgroundColor = editProps.background;
style.top = style.left = "0";
style.right = style.bottom = "100%";
markupDiv.appendChild(editDiv); // add textEditor div to markup div
const divRect = markupDiv.getBoundingClientRect();
const outline = text.getOutline(); // use the outline rather than the text in case it's blank.
text.after(outline); // we have to add it to the DOM or the rbox call doesn't work.
const rbox = outline.rbox();
const bbox = outline.bbox();
outline.remove(); // take it out again.
const editor = this.editor = document.createElement("textarea");
editDiv.appendChild(editor);
editor.className = Markup_1.MarkupApp.textEditorClass;
editor.contentEditable = "true";
editor.spellcheck = true;
editor.wrap = "off";
// so we don't send these events to the ToolAdmin and process them by tools. We want default handling
const mouseListener = (ev) => (ev.stopPropagation(), true);
editor.onselectstart = editor.oncontextmenu = editor.onmousedown = editor.onmouseup = mouseListener; // enable default handling for these events
// Tab, Escape, ctrl-enter, or shift-enter all end the editor
editor.onkeydown = async (ev) => {
if (ev.key === "Tab" || ev.key === "Escape" || (ev.key === "Enter" && (ev.shiftKey || ev.ctrlKey)))
this.exitTool(); // eslint-disable-line @typescript-eslint/no-floating-promises
ev.stopPropagation();
};
const textElStyle = window.getComputedStyle(text.node);
style = editor.style;
style.pointerEvents = "auto";
style.position = "absolute";
style.top = `${(rbox.cy - (bbox.h / 2)) - divRect.top}px`; // put the editor over the middle of the text element
style.left = `${(rbox.cx - (bbox.w / 2)) - divRect.left}px`;
style.height = editProps.size.height;
style.width = editProps.size.width;
style.resize = "both";
style.fontFamily = textElStyle.fontFamily; // set the font family and anchor to the same as the text element
style.textAnchor = textElStyle.textAnchor;
style.fontSize = editProps.fontSize; // from app.props
const parentZ = parseInt(window.getComputedStyle(markupDiv).zIndex || "0", 10);
style.zIndex = (parentZ + 200).toString();
editor.innerHTML = text.getMarkup(); // start with existing text
this.editor.focus(); // give the editor focus
// if we're started from the place text tool, select the entire current value, otherwise place the cursor at the end.
this.editor.setSelectionRange(this._fromPlaceTool ? 0 : editor.value.length, editor.value.length);
}
/** Called when EditText exits, saves the edited value into the text element */
async onCleanup() {
if (!this.editDiv)
return;
const text = this.text;
const original = this.boxed ? this.boxed : text;
const undo = this.markup.undo;
undo.performOperation(this.keyin, () => {
const newVal = this.editor.value;
if (newVal.trim() === "") { // if the result of the editing is blank, just delete the text element
if (!this._fromPlaceTool)
undo.onDelete(original);
original.remove(); // must do this *after* we call undo.onDelete
return;
}
let newText = text.clone();
const fontSize = text.getFontSize();
newText.createMarkup(newVal, fontSize);
if (this.boxed) {
newText = this.createBoxedText(original.parent(), newText);
newText.matrix(original.matrix());
}
original.replace(newText);
if (this._fromPlaceTool)
undo.onAdded(newText);
else
undo.onModified(newText, original);
});
const editSize = Markup_1.MarkupApp.props.text.edit.size;
const style = this.editor.style;
editSize.height = style.height;
editSize.width = style.width;
this.editDiv.remove();
this.editDiv = undefined;
this.editor = undefined;
}
async onInstall() {
if (!await super.onInstall())
return false;
this.startEditor();
return true;
}
async onResetButtonUp(_ev) {
await this.exitTool();
return core_frontend_1.EventHandled.Yes;
}
async onDataButtonUp(_ev) {
await this.exitTool();
return core_frontend_1.EventHandled.Yes;
}
async onMouseStartDrag(_ev) {
await this.exitTool();
return core_frontend_1.EventHandled.Yes;
}
}
exports.EditTextTool = EditTextTool;
//# sourceMappingURL=TextEdit.js.map