UNPKG

suneditor

Version:

Vanilla JavaScript based WYSIWYG web editor

282 lines (241 loc) 9.25 kB
import { keyCodeMap, numbers, dom } from '../../../../helper'; import { Figure } from '../../../../modules/contract'; /** * @class VideoSizeService * @description Handles video size operations including ratio management and size input controls. */ export class VideoSizeService { #main; #$; #state; #pluginOptions; #resizing; #frameRatio; #defaultSizeX; #defaultSizeY; #origin_w; #origin_h; #ratio = { w: 0, h: 0 }; #proportion = null; #inputX = null; #inputY = null; #initRatioValue = null; /** * @param {import('../index').default} main - The main Video plugin instance. * @param {import('../render/video.html').ModalReturns_video} modalEl - Modal element */ constructor(main, modalEl) { this.#main = main; this.#$ = main.$; this.#state = main.state; this.#pluginOptions = main.pluginOptions; this.#resizing = this.#pluginOptions.canResize; this.#frameRatio = this.#state.defaultRatio; this.#defaultSizeX = '100%'; this.#defaultSizeY = this.#pluginOptions.defaultRatio * 100 + '%'; this.#origin_w = this.#pluginOptions.defaultWidth === '100%' ? '' : this.#pluginOptions.defaultWidth; this.#origin_h = this.#pluginOptions.defaultHeight === this.#state.defaultRatio ? '' : this.#pluginOptions.defaultHeight; if (this.#resizing) { this.#proportion = modalEl.proportion; this.frameRatioOption = modalEl.frameRatioOption; this.#inputX = modalEl.inputX; this.#inputY = modalEl.inputY; this.#inputX.value = this.#pluginOptions.defaultWidth; this.#inputY.value = this.#pluginOptions.defaultHeight; const ratioChange = this.#OnChangeRatio.bind(this); this.#$.eventManager.addEvent(this.#inputX, 'keyup', this.#OnInputSize.bind(this, 'x')); this.#$.eventManager.addEvent(this.#inputY, 'keyup', this.#OnInputSize.bind(this, 'y')); this.#$.eventManager.addEvent(this.#inputX, 'change', ratioChange); this.#$.eventManager.addEvent(this.#inputY, 'change', ratioChange); this.#$.eventManager.addEvent(this.#proportion, 'change', ratioChange); this.#$.eventManager.addEvent(this.frameRatioOption, 'change', this.#SetRatio.bind(this)); this.#$.eventManager.addEvent(modalEl.revertBtn, 'click', this.#OnClickRevert.bind(this)); } } /** * @description Sets the width and height input values. * @param {string} w - Width value * @param {string} h - Height value */ setInputSize(w, h) { this.#inputX.value = w === this.#defaultSizeX ? '' : w; if (this.#state.onlyPercentage) return; this.#inputY.value = h === this.#defaultSizeY ? '' : h; } /** * @description Gets the current width and height input values. * @returns {{w: string, h: string}} */ getInputSize() { return { w: this.#inputX?.value || '', h: this.#inputY?.value || '', }; } /** * @description Sets the original width and height of the video. * @param {string} w - Original width * @param {string} h - Original height */ setOriginSize(w, h) { this.#origin_w = w; this.#origin_h = h; } /** * @description Sets the size of the video element. * @param {string|number} w - The width of the video. * @param {string|number} h - The height of the video. */ applySize(w, h) { w ||= this.#inputX?.value || this.#pluginOptions.defaultWidth; h ||= this.#inputY?.value || this.#pluginOptions.defaultHeight; if (this.#state.onlyPercentage) { if (!w) w = '100%'; else if (!/%$/.test(w + '')) w += '%'; } this.#main.figure.setSize(w, h); } /** * @description Resolves the final size of the video element, checking if it has changed from the current size. * @param {string} width - Desired width * @param {string} height - Desired height * @param {HTMLElement} oFrame - The video/iframe element * @param {boolean} isUpdate - Whether we are updating an existing component * @returns {{width: string, height: string, isChanged: boolean}} Resolved size info */ resolveSize(width, height, oFrame, isUpdate) { width ||= this.#defaultSizeX; height ||= this.#frameRatio; const size = this.#main.figure.getSize(oFrame); const inputUpdate = size.w !== width || size.h !== height; const isChanged = !isUpdate || inputUpdate; // set size if (isChanged) { if (this.#initRatioValue !== this.frameRatioOption?.value) this.#main.figure.deleteTransform(); this.applySize(width, height); } return { width, height, isChanged }; } /** * @description Called when the modal is opened. Initializes inputs and ratio options. * @param {boolean} isUpdate - Indicates whether the modal is for editing an existing component (`true`) or registering a new one (`false`). */ on(isUpdate) { if (!this.#resizing) return; if (!isUpdate) { const x = this.#pluginOptions.defaultWidth; const y = this.#pluginOptions.defaultHeight; this.setInputSize(x, y); this.setOriginSize(x, y); this.#proportion.disabled = true; } this.#setRatioSelect(this.#main.figure.isVertical ? '' : this.#origin_h || this.#state.defaultRatio); this.#initRatioValue = this.frameRatioOption?.value; } /** * @description Prepares the size inputs and options when a video component is selected. * @param {import('../../../../modules/contract/Figure').FigureTargetInfo} figureInfo - Figure size information * @param {HTMLElement} target - The selected video/iframe element */ ready(figureInfo, target) { const { dw, dh } = this.#main.figure.getSize(target); this.setInputSize(dw, dh); const h = figureInfo.height || figureInfo.h || this.#origin_h || ''; if (!this.#setRatioSelect(h)) this.#inputY.value = String(this.#state.onlyPercentage ? numbers.get(h, 2) : h); const percentageRotation = this.#state.onlyPercentage && this.#main.figure.isVertical; this.#inputX.disabled = percentageRotation; this.#inputY.disabled = percentageRotation; this.#proportion.checked = !figureInfo.isVertical; this.#proportion.disabled = percentageRotation; this.#ratio = this.#proportion.checked ? figureInfo.ratio : { w: 0, h: 0 }; } /** * @description Initializes the size service state. */ init() { this.#ratio = { w: 0, h: 0 }; if (!this.#resizing) return; this.setInputSize(this.#pluginOptions.defaultWidth, this.#pluginOptions.defaultHeight); this.#proportion.checked = false; this.#proportion.disabled = true; this.#setRatioSelect(this.#state.defaultRatio); } /** * @description Reverts the size inputs to the original video size. */ #OnClickRevert() { if (this.#state.onlyPercentage) { this.#inputX.value = Number(this.#origin_w) > 100 ? '100' : this.#origin_w; } else { this.#inputX.value = this.#origin_w; this.#inputY.value = this.#origin_h; } } /** * @description Selects a ratio option in the ratio dropdown. * @param {string|number} value - The selected ratio value. * @returns {boolean} Returns `true` if a ratio was selected. */ #setRatioSelect(value) { if (!this.frameRatioOption) return; let ratioSelected = false; const ratioOption = this.frameRatioOption.options; if (/%$/.test(value + '') || this.#state.onlyPercentage) value = numbers.get(value, 2) / 100 + ''; else if (!numbers.is(value) || Number(value) >= 1) value = ''; this.#inputY.placeholder = ''; for (let i = 0, len = ratioOption.length; i < len; i++) { if (ratioOption[i].value === value) { ratioSelected = ratioOption[i].selected = true; this.#inputY.placeholder = !value ? '' : Number(value) * 100 + '%'; } else { ratioOption[i].selected = false; } } return ratioSelected; } /** * @description Handles the ratio selection change event. * @param {InputEvent} e - Event object */ #SetRatio(e) { /** @type {HTMLSelectElement} */ const eventTarget = dom.query.getEventTarget(e); const value = eventTarget.options[eventTarget.selectedIndex].value; this.#defaultSizeY = this.#main.figure.autoRatio.current = this.#frameRatio = !value ? this.#defaultSizeY : Number(value) * 100 + '%'; this.#inputY.placeholder = !value ? '' : Number(value) * 100 + '%'; this.#inputY.value = ''; } /** * @description Updates the ratio based on current input values. */ #OnChangeRatio() { this.#ratio = this.#proportion.checked ? Figure.GetRatio(this.#inputX.value, this.#inputY.value, this.#state.sizeUnit) : { w: 0, h: 0 }; } /** * @description Handles keyup events on size inputs to calculate proportion or update ratio selection. * @param {'x'|'y'} xy - Axis (`'x'` for width, `'y'` for height) * @param {KeyboardEvent} e - Event object */ #OnInputSize(xy, e) { if (keyCodeMap.isSpace(e.code)) { e.preventDefault(); return; } /** @type {HTMLInputElement} */ const eventTarget = dom.query.getEventTarget(e); if (xy === 'x' && this.#state.onlyPercentage && Number(eventTarget.value) > 100) { eventTarget.value = '100'; } else if (this.#proportion.checked && !this.frameRatioOption?.value) { const ratioSize = Figure.CalcRatio(this.#inputX.value, this.#inputY.value, this.#state.sizeUnit, this.#ratio); if (xy === 'x') { this.#inputY.value = String(ratioSize.h); } else { this.#inputX.value = String(ratioSize.w); } } if (xy === 'y') { this.#setRatioSelect(eventTarget.value || this.#state.defaultRatio); } } } export default VideoSizeService;