smoosic
Version:
<sub>[Github site](https://github.com/Smoosic/smoosic) | [source documentation](https://smoosic.github.io/Smoosic/release/docs/modules.html) | [change notes](https://aarondavidnewman.github.io/Smoosic/changes.html) | [application](https://smoosic.github.i
477 lines (453 loc) • 17.6 kB
text/typescript
// [Smoosic](https://github.com/AaronDavidNewman/Smoosic)
// Copyright (c) Aaron David Newman 2021.
import { ElementLike, RemoveElementLike } from '../../smo/data/common';
import { SmoScoreText, SmoTextGroup } from '../../smo/data/scoreText';
import { closeDialogPromise } from '../../common/htmlHelpers';
import { layoutDebug } from '../../render/sui/layoutDebug';
import { SvgHelpers, OutlineInfo } from '../../render/sui/svgHelpers';
import { SuiTextEditor } from '../../render/sui/textEdit';
import { DialogDefinition, SuiDialogBase, SuiDialogParams } from './dialog';
import { SuiDragText } from './components/dragText';
import { SuiTextInPlace } from './components/textInPlace';
import { SuiDropdownComponent } from './components/dropdown';
import { SuiToggleComponent } from './components/toggle';
import { SuiRockerComponent } from './components/rocker';
import { SuiHelp } from '../help';
import { SuiFontComponent } from './components/fontComponent';
import { SuiTextBlockComponent } from './components/textInPlace';
import { EventHandler } from '../eventSource';
import { SuiInlineText } from '../../render/sui/textRender';
import { UndoBuffer } from '../../smo/xform/undo';
declare var $: any;
/**
* Complex dialog for managing score text (not associated with music)
* @category SuiDialog
*/
export class SuiTextBlockDialog extends SuiDialogBase {
get textEditorCtrl(): SuiTextInPlace {
return this.cmap.textEditorCtrl as SuiTextInPlace;
}
get insertCodeCtrl(): SuiDropdownComponent {
return this.cmap.insertCodeCtrl as SuiDropdownComponent;
}
get textDraggerCtrl(): SuiDragText {
return this.cmap.textDraggerCtrl as SuiDragText;
}
get yCtrl(): SuiRockerComponent {
return this.cmap.yCtrl as SuiRockerComponent;
}
get xCtrl(): SuiRockerComponent {
return this.cmap.xCtrl as SuiRockerComponent;
}
get fontCtrl(): SuiFontComponent {
return this.cmap.fontCtrl as SuiFontComponent;
}
get textBlockCtrl(): SuiTextBlockComponent {
return this.cmap.textBlockCtrl as SuiTextBlockComponent;
}
get paginationCtrl(): SuiDropdownComponent {
return this.cmap.paginationCtrl as SuiDropdownComponent;
}
get attachToSelectorCtrl(): SuiToggleComponent {
return this.cmap.attachToSelectorCtrl as SuiToggleComponent;
}
static dialogElements: DialogDefinition =
{
label: 'Text Properties', elements:
[{
smoName: 'textEditor',
defaultValue: 0,
control: 'SuiTextInPlace',
classes: 'show-always hide-when-moving',
label: 'Edit Text',
options: []
}, {
smoName: 'insertCode',
classes: 'show-when-editing hide-when-moving',
control: 'SuiDropdownComponent',
label: 'Insert Special',
options: [
{ value: '@@@', label: 'Pages' },
{ value: '###', label: 'Page Number' }
]
}, {
smoName: 'textDragger',
classes: 'hide-when-editing show-when-moving',
defaultValue: 0,
control: 'SuiDragText',
label: 'Move Text',
options: []
}, {
smoName: 'x',
defaultValue: 0,
classes: 'hide-when-editing hide-when-moving',
control: 'SuiRockerComponent',
label: 'X Position (Px)',
dataType: 'int'
}, {
smoName: 'y',
defaultValue: 0,
classes: 'hide-when-editing hide-when-moving',
control: 'SuiRockerComponent',
label: 'Y Position (Px)',
dataType: 'int'
}, {
smoName: 'font',
classes: 'hide-when-editing hide-when-moving',
defaultValue: SmoScoreText.fontFamilies.times,
control: 'SuiFontComponent',
label: 'Font Information'
},
{
smoName: 'textBlock',
classes: 'hide-when-editing hide-when-moving',
defaultValue: '',
control: 'SuiTextBlockComponent',
label: 'Text Block Properties'
},
{ // {every:'every',even:'even',odd:'odd',once:'once'}
smoName: 'pagination',
defaultValue: SmoTextGroup.paginations.ONCE,
classes: 'hide-when-editing hide-when-moving',
control: 'SuiDropdownComponent',
label: 'Page Behavior',
startRow: true,
options: [{ value: SmoTextGroup.paginations.ONCE, label: 'Once' },
{ value: SmoTextGroup.paginations.EVERY, label: 'Every' },
{ value: SmoTextGroup.paginations.ODD, label: 'Odd' },
{ value: SmoTextGroup.paginations.SUBSEQUENT, label: 'Subsequent' }
]
}, {
smoName: 'attachToSelector',
classes: 'hide-when-editing hide-when-moving',
control: 'SuiToggleComponent',
label: 'Attach to Selection'
}],
staticText: [
{ label: 'Text Properties' },
{ editorLabel: 'Done Editing Text' },
{ draggerLabel: 'Done Dragging Text' }
]
};
edited: boolean;
isNew: boolean;
modifier: SmoTextGroup;
originalTextGroup: SmoTextGroup | null = null;
activeScoreText: SmoScoreText;
textElement: any;
mouseMoveHandler: EventHandler | null;
mouseUpHandler: EventHandler | null;
mouseDownHandler: EventHandler | null;
mouseClickHandler: EventHandler | null;
outlineRect: OutlineInfo | null = null;
constructor(parameters: SuiDialogParams) {
let edited = false;
let isNew = false;
const originalTextGroup: SmoTextGroup | null = parameters.modifier ?? null;
const tracker = parameters.view.tracker;
['staffModifier', 'suggestion'].forEach((outlineType) => {
if (tracker.outlines[outlineType]) {
SvgHelpers.eraseOutline(tracker.outlines[outlineType]);
}
});
// Create a new text modifier, if this is new text. Else use selection
if (!parameters.modifier) {
isNew = true;
const textParams = SmoScoreText.defaults;
const newText = new SmoScoreText(textParams);
// convert scroll from screen coord to svg coord
const svgScroll = tracker.renderer.pageMap.clientToSvg(SvgHelpers.smoBox(tracker.scroller.scrollState));
newText.y += svgScroll.y;
newText.x += svgScroll.x;
if (tracker.selections.length > 0) {
const sel = tracker.selections[0].measure.svg;
if (typeof (sel.logicalBox) !== 'undefined') {
if (sel.logicalBox.y >= newText.y) {
newText.y = sel.logicalBox.y;
newText.x = sel.logicalBox.x;
}
}
}
const grpParams = SmoTextGroup.defaults;
grpParams.textBlocks = [{ text: newText, position: SmoTextGroup.relativePositions.LEFT, activeText: true }];
const newGroup = new SmoTextGroup(grpParams);
parameters.modifier = newGroup;
parameters.modifier.setActiveBlock(newText);
parameters.view.groupUndo(true);
parameters.view.addTextGroup(parameters.modifier);
edited = true;
} else {
// Make sure there is a score text to start the editing.
parameters.modifier = SmoTextGroup.deserializePreserveId(parameters.modifier);
parameters.modifier.setActiveBlock(parameters.modifier.textBlocks[0].text);
}
super(SuiTextBlockDialog.dialogElements, parameters);
this.originalTextGroup = originalTextGroup;
this.isNew = isNew;
this.modifier = parameters.modifier;
this.displayOptions = ['BINDCOMPONENTS', 'DRAGGABLE', 'KEYBOARD_CAPTURE', 'MODIFIERPOS']
this.edited = edited;
this.view.groupUndo(true);
this.activeScoreText = this.modifier.getActiveBlock();
this.mouseMoveHandler = null;
this.mouseUpHandler = null;
this.mouseDownHandler = null;
this.mouseClickHandler = null;
}
populateInitial() {
this.textBlockCtrl.setValue({
activeScoreText: this.activeScoreText,
modifier: this.modifier
});
const fontFamily = this.activeScoreText.fontInfo.family;
const fontSize = this.activeScoreText.fontInfo.size;
this.fontCtrl.setValue({
family: fontFamily,
size: fontSize,
style: this.activeScoreText.fontInfo.style,
weight: this.activeScoreText.fontInfo.weight
});
this.attachToSelectorCtrl.setValue(this.modifier.attachToSelector);
const ul = this.modifier.ul();
this.xCtrl.setValue(ul.x);
this.yCtrl.setValue(ul.y);
this.paginationCtrl.setValue(this.modifier.pagination);
this.highlightActiveRegion();
}
static unrenderTextGroup(tg: SmoTextGroup) {
tg.elements.forEach((el: ElementLike) => {
RemoveElementLike(el);
});
tg.elements = [];
}
unrenderOriginal() {
if (this.originalTextGroup) {
SuiTextBlockDialog.unrenderTextGroup(this.originalTextGroup);
}
}
display() {
const pageContext = this.view.renderer.pageMap.getRendererFromModifier(this.activeScoreText);
const svg = pageContext.svg;
this.textElement = $(svg).find('.' + this.activeScoreText.attrs.id)[0];
$('body').addClass('showAttributeDialog');
$('body').addClass('textEditor');
this.applyDisplayOptions();
this.populateInitial();
this.bindElements();
if (!this.modifier.logicalBox) {
this.view.renderer.renderTextGroup(this.modifier);
}
// If this control has not been edited this session, assume they want to
// edit the text and just right into that.
if (!this.modifier.edited) {
this.modifier.edited = true;
layoutDebug.addDialogDebug('text transform db: startEditSession');
this.unrenderOriginal();
this.textEditorCtrl.startEditSession();
}
const mousemove = async (ev: any) => {
this.mouseMove(ev);
}
const mouseup = async (ev: any) => {
this.mouseUp();
}
const mousedown = async (ev: any) => {
this.mouseDown(ev);
}
const mouseclick = async (ev: any) => {
this.mouseClick(ev);
}
this.mouseMoveHandler = this.eventSource.bindMouseMoveHandler(mousemove);
this.mouseUpHandler = this.eventSource.bindMouseUpHandler(mouseup);
this.mouseDownHandler = this.eventSource.bindMouseDownHandler(mousedown);
this.mouseClickHandler = this.eventSource.bindMouseClickHandler(mouseclick);
}
_resetAttachToSelector() {
this.modifier.attachToSelector = false;
this.modifier.selector = SmoTextGroup.defaults.selector;
this.modifier.musicXOffset = SmoTextGroup.defaults.musicXOffset;
this.modifier.musicYOffset = SmoTextGroup.defaults.musicYOffset;
}
_activateAttachToSelector() {
this.modifier.attachToSelector = true;
this.modifier.selector = JSON.parse(JSON.stringify(this.view.tracker.selections[0].selector));
if (this.modifier.logicalBox) {
this.modifier.musicXOffset = this.modifier.logicalBox.x - this.view.tracker.selections[0].measure.svg.logicalBox.x;
this.modifier.musicYOffset = this.modifier.logicalBox.y - this.view.tracker.selections[0].measure.svg.logicalBox.y;
}
}
changed() {
this.edited = true;
if (this.insertCodeCtrl.changeFlag && this.textEditorCtrl.session) {
const val = this.insertCodeCtrl.getValue().toString().split('');
val.forEach((key) => {
this.evKey({ key });
});
this.insertCodeCtrl.unselect();
}
if (this.textBlockCtrl.changeFlag) {
const nval = this.textBlockCtrl.getValue();
this.activeScoreText = nval.activeScoreText;
this.highlightActiveRegion();
}
if (this.textEditorCtrl.changeFlag) {
this.highlightActiveRegion();
}
if (this.attachToSelectorCtrl.changeFlag) {
const toSet = this.attachToSelectorCtrl.getValue();
if (toSet) {
this._activateAttachToSelector();
this.paginationCtrl.setValue(SmoTextGroup.paginations.ONCE);
this.modifier.pagination = SmoTextGroup.paginations.ONCE;
} else {
this._resetAttachToSelector();
}
}
const pos = this.modifier.ul();
// position can change from drag or by dialog - only update from
// dialog entries if that changed.
if (this.xCtrl.changeFlag) {
this.modifier.offsetX(this.xCtrl.getValue() - pos.x);
}
if (this.yCtrl.changeFlag) {
this.modifier.offsetY(this.yCtrl.getValue() - pos.y);
}
if (this.textDraggerCtrl.changeFlag) {
this.xCtrl.setValue(pos.x);
this.yCtrl.setValue(pos.y);
}
if (this.paginationCtrl.changeFlag) {
this.modifier.pagination = parseInt(this.paginationCtrl.getValue().toString(), 10);
// Pagination and attach to measure don't mix.
this._resetAttachToSelector();
this.attachToSelectorCtrl.setValue(false);
}
if (this.fontCtrl.changeFlag) {
const fontInfo = this.fontCtrl.getValue();
this.activeScoreText.fontInfo.family = fontInfo.family;
// transitioning away from non-point-based font size units
this.activeScoreText.fontInfo.size = fontInfo.size;
this.activeScoreText.fontInfo.weight = fontInfo.weight;
this.activeScoreText.fontInfo.style = fontInfo.style;
}
// Use layout context because render may have reset svg.
const subtype = this.isNew ? UndoBuffer.bufferSubtypes.ADD : UndoBuffer.bufferSubtypes.UPDATE;
this.view.updateTextGroup(this.modifier);
}
highlightActiveRegion() {
const pageContext = this.view.renderer.pageMap.getRendererFromModifier(this.activeScoreText);
const svg = pageContext.svg;
if (this.activeScoreText.logicalBox) {
const stroke = SuiTextEditor.strokes['text-highlight'];
if (!this.outlineRect) {
this.outlineRect = {
context: pageContext,
classes: '',
stroke,
box: this.activeScoreText.logicalBox,
scroll: this.scroller.scrollState,
timeOff: 1000
};
}
SvgHelpers.eraseOutline(this.outlineRect);
this.outlineRect.box = this.activeScoreText.logicalBox;
SvgHelpers.outlineRect(this.outlineRect);
}
}
// ### handleKeydown
// allow a dialog to be dismissed by esc.
evKey(evdata: any) {
if (evdata.key === 'Escape') {
$(this.dgDom.element).find('.cancel-button').click();
evdata.preventDefault();
} else {
this.textEditorCtrl.evKey(evdata);
}
}
// ### Event handlers, passed from dialog
mouseUp() {
if (this.textDraggerCtrl && this.textDraggerCtrl.running) {
this.textDraggerCtrl.mouseUp(null);
}
}
mouseMove(ev: any) {
if (this.textDraggerCtrl && this.textDraggerCtrl.running) {
this.textDraggerCtrl.mouseMove(ev);
} else if (this.textEditorCtrl && this.textEditorCtrl.isRunning) {
this.textEditorCtrl.mouseMove(ev);
}
}
mouseClick(ev: any) {
if (this.textEditorCtrl && this.textEditorCtrl.isRunning) {
this.textEditorCtrl.mouseClick(ev);
ev.stopPropagation();
}
}
mouseDown(ev: any) {
if (this.textDraggerCtrl && this.textDraggerCtrl.running) {
this.textDraggerCtrl.mouseDown(ev);
}
}
_complete() {
this.view.groupUndo(false);
this.modifier.setActiveBlock(null);
this.view.tracker.updateMap(); // update the text map
this.view.renderer.setDirty();
if (this.mouseDownHandler) {
this.eventSource.unbindMouseDownHandler(this.mouseDownHandler);
}
if (this.mouseUpHandler) {
this.eventSource.unbindMouseUpHandler(this.mouseUpHandler);
}
if (this.mouseMoveHandler) {
this.eventSource.unbindMouseMoveHandler(this.mouseMoveHandler);
}
if (this.mouseClickHandler) {
this.eventSource.unbindMouseClickHandler(this.mouseClickHandler);
}
if (this.outlineRect) {
SvgHelpers.eraseOutline(this.outlineRect);
}
// Hack - this comes from SuiInlineText and SuiTextEdit.
$('body').removeClass('showAttributeDialog');
$('body').removeClass('textEditor');
this.complete();
}
_removeText() {
// The modifier rendered is for edit, not the one attached to the score. so
// unrender it now
SuiTextBlockDialog.unrenderTextGroup(this.modifier);
this.view.removeTextGroup(this.modifier);
}
bindElements() {
const dgDom = this.dgDom;
$(dgDom.element).find('.ok-button').off('click').on('click', () => {
const subtype = this.isNew ? UndoBuffer.bufferSubtypes.ADD : UndoBuffer.bufferSubtypes.UPDATE;
this.view.updateTextGroup(this.modifier);
this._complete();
});
$(dgDom.element).find('.cancel-button').off('click').on('click', () => {
if (this.edited) {
this.modifier.elements.forEach((element: ElementLike) => {
RemoveElementLike(element);
});
this.modifier.elements = [];
this.view.undo();
}
this._complete();
});
$(dgDom.element).find('.remove-button').off('click').on('click', () => {
this._removeText();
this._complete();
});
}
}
/**
* @category SuiDialog
*/
export class helpModal {
static createAndDisplay() {
SuiHelp.displayHelp();
return closeDialogPromise();
}
}