blob: 2442b62617b81c79ddd35dd6f2c422e6c35ccc39 [file] [log] [blame]
/**
* @module skottie-text-editor-box-sk
* @description <h2><code>skottie-text-editor-box-sk</code></h2>
*
* <p>
* A skottie text editor box
* </p>
*
*
* @evt change - This event is generated when the text changes.
*
* @evt font-change - This event is generated when the font changes.
*
* @attr textData the text data.
* At the moment it only reads it at load time.
*
* @attr mode - the view mode.
* Supported values are default and presentation
*
*/
import { html, TemplateResult } from 'lit/html.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { define } from '../../../../elements-sk/modules/define';
import { ElementSk } from '../../../../infra-sk/modules/ElementSk';
import { ViewMode } from '../../types';
import { ExtraLayerData, TextData } from '../text-replace';
import sanitizeText from '../text-sanizite';
import '../../skottie-font-selector-sk';
import '../../skottie-text-sampler-sk';
import {
FontType,
SkottieFontEventDetail,
} from '../../skottie-font-selector-sk/skottie-font-selector-sk';
import { SkTextSampleEventDetail } from '../../skottie-text-sampler-sk/skottie-text-sampler-sk';
export interface SkottieFontChangeEventDetail {
font: FontType;
fontName: string;
}
export class SkottieTextEditorBoxSk extends ElementSk {
private static template = (ele: SkottieTextEditorBoxSk) => html`
<li class="wrapper">
<details class="expando" ?open=${ele._isOpen} @toggle=${ele.toggle}>
<summary>
<span class="summary-label"> Text layer: ${ele._textData?.text || ''} </span>
<expand-less-icon-sk></expand-less-icon-sk>
<expand-more-icon-sk></expand-more-icon-sk>
</summary>
<div class="text-element">
${ele.textElementTitle()}
<textarea
class="text-element-input"
@change=${ele.onChange}
@input=${ele.onChange}
@blur=${ele.onBlur}
maxlength=${ifDefined(ele._textData?.maxChars)}
.value=${ele._textData?.text || ''}></textarea>
<div>${ele.originTemplate()}</div>
<div>${ele.fontSelector()}</div>
<div>${ele.fontSettings()}</div>
<div>${ele.fontTextSampler()}</div>
</div>
</details>
</li>
`;
private _textData: TextData | null = null;
private mode: ViewMode = 'default';
private _isOpen: boolean = false;
private _timeoutId: number = -1;
constructor() {
super(SkottieTextEditorBoxSk.template);
}
connectedCallback(): void {
super.connectedCallback();
this._render();
}
private onChange(ev: Event): void {
if (this._textData) {
const target = ev.target as HTMLTextAreaElement;
const text = sanitizeText(target.value);
this._textData.text = text;
this._textData.items.forEach((item: ExtraLayerData) => {
// this property is the text string of a text layer.
// It's read as: Text Element > Text document > First Keyframe > Start Value > Text
if (item.layer.t) {
item.layer.t.d.k[0].s.t = text;
}
});
this.scheduleChangeEvent();
}
}
private onBlur(): void {
this.scheduleChangeEvent(0);
}
// instead of dispatching a change event on every keystroke,
// we buffer changes by default for 1 second
// to avoid performance issues when rebuilding the animation
private scheduleChangeEvent(timeout: number = 1000): void {
window.clearTimeout(this._timeoutId);
this._timeoutId = window.setTimeout(() => {
this.dispatchEvent(
new CustomEvent('text-data-change', {
detail: {},
})
);
}, timeout);
}
private toggle(): void {
this._isOpen = !this._isOpen;
this._render();
}
private textElementTitle(): TemplateResult | null {
if (this.mode === 'presentation' || !this._textData) {
return null;
}
const { name } = this._textData;
return html`
<div class="text-element-item">
<div class="text-element-label">Layer name:</div>
<div>${name}</div>
</div>
`;
}
private originTemplate(): TemplateResult | null {
if (this.mode === 'presentation' || !this._textData) {
return null;
}
const { items } = this._textData;
return html`
<div class="text-element-section">
<div class="text-element-section--title">Origin${items.length > 1 ? 's' : ''}:</div>
<ul>
${items.map(SkottieTextEditorBoxSk.originTemplateElement)}
</ul>
</div>
`;
}
private fontSelector(): TemplateResult | null {
if (this.mode === 'presentation' || !this._textData) {
return null;
}
const { items, fontName } = this._textData;
return html`
<section class="text-element-section">
<div class="text-element-section--title">Font manager</div>
<skottie-font-selector-sk .fontName=${fontName} @select-font=${this.onFontSelected}>
</skottie-font-selector-sk>
</section>
`;
}
private fontSettings(): TemplateResult | null {
if (this.mode === 'presentation' || !this._textData) {
return null;
}
return html`
<section class="text-element-section">
<div class="text-element-section--title">Font settings</div>
<div class="text-box text-box__left">
<div class="text-box--label">
<span class="icon-sk text-box--label--icon"> format_line_spacing </span>
</div>
<input
type="number"
class="text-box--input"
@change=${this.onLineHeightChange}
value=${this._textData.lineHeight}
required />
</div>
<div class="text-box text-box__left">
<div class="text-box--label">
<span class="icon-sk text-box--label--icon"> format_letter_spacing_wide </span>
</div>
<input
type="number"
class="text-box--input"
@change=${this.onTrackingChange}
value=${this._textData.tracking}
required />
</div>
</section>
`;
}
private fontTextSampler(): TemplateResult | null {
if (this.mode === 'presentation' || !this._textData) {
return null;
}
const { fontName } = this._textData;
return html`
<section class="text-element-section">
<div class="text-element-section--title">Text samples</div>
<skottie-text-sampler-sk .fontName=${fontName} @select-text=${this.onTextSelected}>
</skottie-text-sampler-sk>
</section>
`;
}
private static originTemplateElement(item: ExtraLayerData): TemplateResult | null {
return html`
<li class="text-element-origin"><b>${item.precompName}</b> > Layer ${item.layer.ind}</li>
`;
}
private onFontSelected(ev: CustomEvent<SkottieFontEventDetail>): void {
if (this._textData) {
this.dispatchEvent(
new CustomEvent<SkottieFontChangeEventDetail>('font-change', {
detail: {
font: ev.detail.font,
fontName: this._textData?.fontName || '',
},
})
);
this._textData.fontName = ev.detail.font.fName;
}
}
private onTextSelected(ev: CustomEvent<SkTextSampleEventDetail>): void {
if (this._textData) {
if (ev.detail.font) {
this.dispatchEvent(
new CustomEvent<SkottieFontChangeEventDetail>('font-change', {
detail: {
font: ev.detail.font,
fontName: this._textData?.fontName || '',
},
})
);
}
this._textData.text = ev.detail.text;
this._textData.items.forEach((item: ExtraLayerData) => {
// this property is the text string of a text layer.
// It's read as: Text Element > Text document > First Keyframe > Start Value > Text
if (item.layer.t) {
item.layer.t.d.k[0].s.t = ev.detail.text;
}
});
this.scheduleChangeEvent(0);
}
}
private onTrackingChange(ev: Event) {
const target = ev.target as HTMLInputElement;
if (this._textData) {
this._textData.tracking = target.valueAsNumber;
this._textData.items.forEach((item: ExtraLayerData) => {
// this property is the text tracking of a text layer.
// It's read as: Text Element > Text document > First Keyframe > Start Value > Tracking
if (item.layer.t) {
item.layer.t.d.k[0].s.tr = target.valueAsNumber;
}
});
this.scheduleChangeEvent(0);
}
}
private onLineHeightChange(ev: Event) {
const target = ev.target as HTMLInputElement;
if (this._textData) {
this._textData.lineHeight = target.valueAsNumber;
this._textData.items.forEach((item: ExtraLayerData) => {
// this property is the text line height of a text layer.
// It's read as: Text Element > Text document > First Keyframe > Start Value > Line height
if (item.layer.t) {
item.layer.t.d.k[0].s.lh = target.valueAsNumber;
}
});
this.scheduleChangeEvent(0);
}
}
set textData(val: TextData) {
this._textData = val;
this._render();
}
}
define('skottie-text-editor-box-sk', SkottieTextEditorBoxSk);