|
1 | | -import Resizer, { ResizerOptions } from '../../utils/Resizer'; |
| 1 | +import { LiteralUnion, Position } from '../../common'; |
| 2 | +import Component from '../../dom_components/model/Component'; |
| 3 | +import { ComponentsEvents } from '../../dom_components/types'; |
| 4 | +import ComponentView from '../../dom_components/view/ComponentView'; |
| 5 | +import StyleableModel, { StyleProps } from '../../domain_abstract/model/StyleableModel'; |
| 6 | +import { getUnitFromValue } from '../../utils/mixins'; |
| 7 | +import Resizer, { RectDim, ResizerOptions } from '../../utils/Resizer'; |
2 | 8 | import { CommandObject } from './CommandAbstract'; |
3 | 9 |
|
| 10 | +export interface ComponentResizeOptions extends ResizerOptions { |
| 11 | + component: Component; |
| 12 | + componentView?: ComponentView; |
| 13 | + el?: HTMLElement; |
| 14 | + afterStart?: () => void; |
| 15 | + afterEnd?: () => void; |
| 16 | + /** |
| 17 | + * When the element is using an absolute position, the resizer, by default, will try to |
| 18 | + * update position values (eg. 'top'/'left') |
| 19 | + */ |
| 20 | + skipPositionUpdate?: boolean; |
| 21 | + /** |
| 22 | + * @deprecated |
| 23 | + */ |
| 24 | + options?: ResizerOptions; |
| 25 | +} |
| 26 | + |
| 27 | +export interface ComponentResizeModelProperty { |
| 28 | + value: string; |
| 29 | + property: string; |
| 30 | + number: number; |
| 31 | + unit: string; |
| 32 | +} |
| 33 | + |
| 34 | +export interface ComponentResizeEventProps { |
| 35 | + component: Component; |
| 36 | + event: PointerEvent; |
| 37 | + el: HTMLElement; |
| 38 | + rect: RectDim; |
| 39 | +} |
| 40 | + |
| 41 | +export interface ComponentResizeEventStartProps extends ComponentResizeEventProps { |
| 42 | + model: StyleableModel; |
| 43 | + modelWidth: ComponentResizeModelProperty; |
| 44 | + modelHeight: ComponentResizeModelProperty; |
| 45 | +} |
| 46 | + |
| 47 | +export interface ComponentResizeEventMoveProps extends ComponentResizeEventProps { |
| 48 | + delta: Position; |
| 49 | + pointer: Position; |
| 50 | +} |
| 51 | + |
| 52 | +export interface ComponentResizeEventEndProps extends ComponentResizeEventProps { |
| 53 | + moved: boolean; |
| 54 | +} |
| 55 | + |
| 56 | +export interface ComponentResizeEventUpdateProps extends ComponentResizeEventProps { |
| 57 | + partial: boolean; |
| 58 | + delta: Position; |
| 59 | + pointer: Position; |
| 60 | + style: StyleProps; |
| 61 | + updateStyle: (styles?: StyleProps) => void; |
| 62 | + convertPxToUnit: (props: ConvertPxToUnitProps) => string; |
| 63 | +} |
| 64 | + |
| 65 | +export interface ConvertPxToUnitProps { |
| 66 | + el: HTMLElement; |
| 67 | + valuePx: number; |
| 68 | + unit?: LiteralUnion<ConvertUnitsToPx, string>; |
| 69 | + /** |
| 70 | + * @default 3 |
| 71 | + */ |
| 72 | + roundDecimals?: number; |
| 73 | + /** |
| 74 | + * DPI (Dots Per Inch) value to use for conversion. |
| 75 | + * @default 96 |
| 76 | + */ |
| 77 | + dpi?: number; |
| 78 | +} |
| 79 | + |
| 80 | +export enum ConvertUnitsToPx { |
| 81 | + pt = 'pt', |
| 82 | + pc = 'pc', |
| 83 | + in = 'in', |
| 84 | + cm = 'cm', |
| 85 | + mm = 'mm', |
| 86 | + vw = 'vw', |
| 87 | + vh = 'vh', |
| 88 | + vmin = 'vmin', |
| 89 | + vmax = 'vmax', |
| 90 | + svw = 'svw', |
| 91 | + lvw = 'lvw', |
| 92 | + dvw = 'dvw', |
| 93 | + svh = 'svh', |
| 94 | + lvh = 'lvh', |
| 95 | + dvh = 'dvh', |
| 96 | + perc = '%', |
| 97 | +} |
| 98 | + |
4 | 99 | export default { |
5 | | - run(editor, sender, opts) { |
6 | | - const opt = opts || {}; |
7 | | - const canvas = editor.Canvas; |
8 | | - const canvasView = canvas.getCanvasView(); |
9 | | - const options: ResizerOptions = { |
10 | | - appendTo: canvas.getResizerEl(), |
| 100 | + run(editor, _, options: ComponentResizeOptions) { |
| 101 | + const { Canvas, Utils, em } = editor; |
| 102 | + const canvasView = Canvas.getCanvasView(); |
| 103 | + const pfx = em.config.stylePrefix || ''; |
| 104 | + const resizeClass = `${pfx}resizing`; |
| 105 | + const { |
| 106 | + onStart = () => {}, |
| 107 | + onMove = () => {}, |
| 108 | + onEnd = () => {}, |
| 109 | + updateTarget = () => {}, |
| 110 | + el: elOpts, |
| 111 | + componentView, |
| 112 | + component, |
| 113 | + skipPositionUpdate, |
| 114 | + ...resizableOpts |
| 115 | + } = options; |
| 116 | + const el = elOpts || componentView?.el || component.getEl()!; |
| 117 | + const resizeEventOpts = { component, el }; |
| 118 | + let modelToStyle: StyleableModel; |
| 119 | + |
| 120 | + const toggleBodyClass = (method: string, e: any, opts: any) => { |
| 121 | + const docs = opts.docs; |
| 122 | + docs && |
| 123 | + docs.forEach((doc: Document) => { |
| 124 | + const body = doc.body; |
| 125 | + const cls = body.className || ''; |
| 126 | + body.className = (method == 'add' ? `${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim(); |
| 127 | + }); |
| 128 | + }; |
| 129 | + |
| 130 | + const resizeOptions: ResizerOptions = { |
| 131 | + appendTo: Canvas.getResizerEl(), |
11 | 132 | prefix: editor.getConfig().stylePrefix, |
12 | 133 | posFetcher: canvasView.getElementPos.bind(canvasView), |
13 | | - mousePosFetcher: canvas.getMouseRelativePos.bind(canvas), |
14 | | - ...(opt.options || {}), |
| 134 | + mousePosFetcher: Canvas.getMouseRelativePos.bind(Canvas), |
| 135 | + docs: [document], |
| 136 | + onStart(ev, opts) { |
| 137 | + onStart(ev, opts); |
| 138 | + const { el, config, resizer } = opts; |
| 139 | + const { keyHeight, keyWidth, currentUnit, keepAutoHeight, keepAutoWidth } = config; |
| 140 | + toggleBodyClass('add', ev, opts); |
| 141 | + modelToStyle = em.Styles.getModelToStyle(component); |
| 142 | + const computedStyle = getComputedStyle(el); |
| 143 | + const modelStyle = modelToStyle.getStyle(); |
| 144 | + const rectStart = { ...resizer.startDim! }; |
| 145 | + |
| 146 | + let currentWidth = modelStyle[keyWidth!] as string; |
| 147 | + config.autoWidth = keepAutoWidth && currentWidth === 'auto'; |
| 148 | + if (isNaN(parseFloat(currentWidth))) { |
| 149 | + currentWidth = computedStyle[keyWidth as any]; |
| 150 | + } |
| 151 | + |
| 152 | + let currentHeight = modelStyle[keyHeight!] as string; |
| 153 | + config.autoHeight = keepAutoHeight && currentHeight === 'auto'; |
| 154 | + if (isNaN(parseFloat(currentHeight))) { |
| 155 | + currentHeight = computedStyle[keyHeight as any]; |
| 156 | + } |
| 157 | + |
| 158 | + const valueWidth = parseFloat(currentWidth); |
| 159 | + const valueHeight = parseFloat(currentHeight); |
| 160 | + const unitWidth = getUnitFromValue(currentWidth); |
| 161 | + const unitHeight = getUnitFromValue(currentHeight); |
| 162 | + |
| 163 | + if (currentUnit) { |
| 164 | + config.unitWidth = unitWidth; |
| 165 | + config.unitHeight = unitHeight; |
| 166 | + } |
| 167 | + |
| 168 | + const eventProps: ComponentResizeEventStartProps = { |
| 169 | + ...resizeEventOpts, |
| 170 | + event: ev, |
| 171 | + rect: rectStart, |
| 172 | + model: modelToStyle, |
| 173 | + modelWidth: { |
| 174 | + value: currentWidth, |
| 175 | + property: keyWidth!, |
| 176 | + number: valueWidth, |
| 177 | + unit: unitWidth, |
| 178 | + }, |
| 179 | + modelHeight: { |
| 180 | + value: currentHeight, |
| 181 | + property: keyHeight!, |
| 182 | + number: valueHeight, |
| 183 | + unit: unitHeight, |
| 184 | + }, |
| 185 | + }; |
| 186 | + editor.trigger(ComponentsEvents.resizeStart, eventProps); |
| 187 | + editor.trigger(ComponentsEvents.resize, { ...eventProps, type: 'start' }); |
| 188 | + options.afterStart?.(); |
| 189 | + }, |
| 190 | + |
| 191 | + // Update all positioned elements (eg. component toolbar) |
| 192 | + onMove(event, opts) { |
| 193 | + onMove(event, opts); |
| 194 | + const { resizer } = opts; |
| 195 | + const eventProps: ComponentResizeEventMoveProps = { |
| 196 | + ...resizeEventOpts, |
| 197 | + event, |
| 198 | + delta: resizer.delta!, |
| 199 | + pointer: resizer.currentPos!, |
| 200 | + rect: resizer.rectDim!, |
| 201 | + }; |
| 202 | + editor.trigger(ComponentsEvents.resizeStart, eventProps); |
| 203 | + editor.trigger(ComponentsEvents.resize, { ...eventProps, type: 'move' }); |
| 204 | + }, |
| 205 | + |
| 206 | + onEnd(event, opts) { |
| 207 | + onEnd(event, opts); |
| 208 | + toggleBodyClass('remove', event, opts); |
| 209 | + const { resizer } = opts; |
| 210 | + const eventProps: ComponentResizeEventEndProps = { |
| 211 | + ...resizeEventOpts, |
| 212 | + event, |
| 213 | + rect: resizer.rectDim!, |
| 214 | + moved: resizer.moved, |
| 215 | + }; |
| 216 | + editor.trigger(ComponentsEvents.resizeEnd, eventProps); |
| 217 | + editor.trigger(ComponentsEvents.resize, { ...resizeEventOpts, type: 'end' }); |
| 218 | + options.afterEnd?.(); |
| 219 | + }, |
| 220 | + |
| 221 | + updateTarget: (_el, rect, options) => { |
| 222 | + updateTarget(_el, rect, options); |
| 223 | + if (!modelToStyle) { |
| 224 | + return; |
| 225 | + } |
| 226 | + |
| 227 | + const { store, selectedHandler, config, resizer, event } = options; |
| 228 | + const { keyHeight, keyWidth, autoHeight, autoWidth, unitWidth, unitHeight } = config; |
| 229 | + const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler!) >= 0; |
| 230 | + const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler!) >= 0; |
| 231 | + const partial = !store; |
| 232 | + const style: StyleProps = {}; |
| 233 | + |
| 234 | + if (!onlyHeight) { |
| 235 | + const bodyw = Canvas.getBody()?.offsetWidth || 0; |
| 236 | + const width = rect.w < bodyw ? rect.w : bodyw; |
| 237 | + style[keyWidth!] = autoWidth |
| 238 | + ? 'auto' |
| 239 | + : this.convertPxToUnit({ |
| 240 | + el, |
| 241 | + valuePx: width, |
| 242 | + unit: unitWidth, |
| 243 | + }); |
| 244 | + } |
| 245 | + |
| 246 | + if (!onlyWidth) { |
| 247 | + style[keyHeight!] = autoHeight |
| 248 | + ? 'auto' |
| 249 | + : this.convertPxToUnit({ |
| 250 | + el, |
| 251 | + valuePx: rect.h, |
| 252 | + unit: unitHeight, |
| 253 | + }); |
| 254 | + } |
| 255 | + |
| 256 | + if (!skipPositionUpdate && em.getDragMode(component)) { |
| 257 | + style.top = `${rect.t}px`; |
| 258 | + style.left = `${rect.l}px`; |
| 259 | + } |
| 260 | + |
| 261 | + let styleUpdated = false; |
| 262 | + |
| 263 | + const updateStyle = (customStyle?: StyleProps) => { |
| 264 | + styleUpdated = true; |
| 265 | + const finalStyle = { ...(customStyle || style), __p: partial }; |
| 266 | + modelToStyle.addStyle(finalStyle, { avoidStore: partial }); |
| 267 | + em.Styles.__emitCmpStyleUpdate(finalStyle as any, { components: component }); |
| 268 | + }; |
| 269 | + |
| 270 | + const eventProps: ComponentResizeEventUpdateProps = { |
| 271 | + ...resizeEventOpts, |
| 272 | + rect, |
| 273 | + partial, |
| 274 | + event, |
| 275 | + style, |
| 276 | + updateStyle, |
| 277 | + convertPxToUnit: (props: Omit<ConvertPxToUnitProps, 'el'>) => this.convertPxToUnit({ el, ...props }), |
| 278 | + delta: resizer.delta!, |
| 279 | + pointer: resizer.currentPos!, |
| 280 | + }; |
| 281 | + editor.trigger(ComponentsEvents.resizeUpdate, eventProps); |
| 282 | + !styleUpdated && updateStyle(); |
| 283 | + }, |
| 284 | + ...resizableOpts, |
| 285 | + ...options.options, |
15 | 286 | }; |
| 287 | + |
16 | 288 | let { canvasResizer } = this; |
17 | 289 |
|
18 | 290 | // Create the resizer for the canvas if not yet created |
19 | | - if (!canvasResizer || opt.forceNew) { |
20 | | - this.canvasResizer = new editor.Utils.Resizer(options); |
| 291 | + if (!canvasResizer) { |
| 292 | + this.canvasResizer = new Utils.Resizer(resizeOptions); |
21 | 293 | canvasResizer = this.canvasResizer; |
22 | 294 | } |
23 | 295 |
|
24 | | - canvasResizer.setOptions(options, true); |
| 296 | + canvasResizer.setOptions(resizeOptions, true); |
25 | 297 | canvasResizer.blur(); |
26 | | - canvasResizer.focus(opt.el); |
| 298 | + canvasResizer.focus(el); |
27 | 299 | return canvasResizer; |
28 | 300 | }, |
29 | 301 |
|
30 | 302 | stop() { |
31 | 303 | this.canvasResizer?.blur(); |
32 | 304 | }, |
33 | | -} as CommandObject<{ options?: {}; forceNew?: boolean; el: HTMLElement }, { canvasResizer?: Resizer }>; |
| 305 | + |
| 306 | + convertPxToUnit(props: ConvertPxToUnitProps): string { |
| 307 | + const { el, valuePx, unit, dpi = 96, roundDecimals = 3 } = props; |
| 308 | + const win = el.ownerDocument.defaultView; |
| 309 | + const winWidth = win?.innerWidth || 1; |
| 310 | + const winHeight = window.innerHeight || 1; |
| 311 | + let valueResult = valuePx; |
| 312 | + let untiResult = unit; |
| 313 | + |
| 314 | + switch (unit) { |
| 315 | + case ConvertUnitsToPx.pt: |
| 316 | + valueResult = valuePx * (72 / dpi); |
| 317 | + break; |
| 318 | + case ConvertUnitsToPx.pc: |
| 319 | + valueResult = valuePx * (6 / dpi); |
| 320 | + break; |
| 321 | + case ConvertUnitsToPx.in: |
| 322 | + valueResult = valuePx / dpi; |
| 323 | + break; |
| 324 | + case ConvertUnitsToPx.cm: |
| 325 | + valueResult = valuePx / (dpi / 2.54); |
| 326 | + break; |
| 327 | + case ConvertUnitsToPx.mm: |
| 328 | + valueResult = valuePx / (dpi / 25.4); |
| 329 | + break; |
| 330 | + case ConvertUnitsToPx.vw: |
| 331 | + valueResult = (valuePx / winWidth) * 100; |
| 332 | + break; |
| 333 | + case ConvertUnitsToPx.vh: |
| 334 | + valueResult = (valuePx / winHeight) * 100; |
| 335 | + break; |
| 336 | + case ConvertUnitsToPx.vmin: { |
| 337 | + const vmin = Math.min(winWidth, winHeight); |
| 338 | + valueResult = (valuePx / vmin) * 100; |
| 339 | + break; |
| 340 | + } |
| 341 | + case ConvertUnitsToPx.vmax: { |
| 342 | + const vmax = Math.max(winWidth, winHeight); |
| 343 | + valueResult = (valuePx / vmax) * 100; |
| 344 | + break; |
| 345 | + } |
| 346 | + case ConvertUnitsToPx.perc: { |
| 347 | + const parentSize = el.parentElement?.offsetWidth || 1; |
| 348 | + valueResult = (valuePx / parentSize) * 100; |
| 349 | + break; |
| 350 | + } |
| 351 | + case ConvertUnitsToPx.svw: |
| 352 | + case ConvertUnitsToPx.lvw: |
| 353 | + case ConvertUnitsToPx.dvw: |
| 354 | + valueResult = (valuePx / winWidth) * 100; |
| 355 | + break; |
| 356 | + case ConvertUnitsToPx.svh: |
| 357 | + case ConvertUnitsToPx.lvh: |
| 358 | + case ConvertUnitsToPx.dvh: |
| 359 | + valueResult = (valuePx / winHeight) * 100; |
| 360 | + break; |
| 361 | + default: |
| 362 | + untiResult = 'px'; |
| 363 | + } |
| 364 | + |
| 365 | + return `${+valueResult.toFixed(roundDecimals)}${untiResult}`; |
| 366 | + }, |
| 367 | +} as CommandObject< |
| 368 | + ComponentResizeOptions, |
| 369 | + { |
| 370 | + canvasResizer?: Resizer; |
| 371 | + convertPxToUnit: (props: ConvertPxToUnitProps) => string; |
| 372 | + } |
| 373 | +>; |
0 commit comments