All files / roosterjs-content-model-plugins/lib/imageEdit/Cropper createImageCropper.ts

100% Statements 31/31
84.21% Branches 16/19
100% Functions 11/11
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 941x 1x 1x     1x                     1x 3x   15x 15x         15x     15x 3x             1x 3x         3x             3x 3x 12x     3x       12x 12x 12x   12x                   12x 12x 24x 48x     12x         48x     48x   48x          
import { createElement } from '../../pluginUtils/CreateElement/createElement';
import { ImageEditElementClass } from '../types/ImageEditElementClass';
import { isElementOfType, isNodeOfType } from 'roosterjs-content-model-dom';
import type { CreateElementData } from '../../pluginUtils/CreateElement/CreateElementData';
import type { DNDDirectionX, DnDDirectionY } from '../types/DragAndDropContext';
import {
    CROP_HANDLE_SIZE,
    CROP_HANDLE_WIDTH,
    ROTATION,
    XS_CROP,
    YS_CROP,
} from '../constants/constants';
 
/**
 * @internal
 */
export function createImageCropper(doc: Document) {
    const cropper = getCropHTML()
        .map(data => {
            const cropper = createElement(data, doc);
            Eif (
                cropper &&
                isNodeOfType(cropper, 'ELEMENT_NODE') &&
                isElementOfType(cropper, 'div')
            ) {
                return cropper;
            }
        })
        .filter(cropper => !!cropper) as HTMLDivElement[];
    return cropper;
}
 
/**
 * @internal
 * Get HTML for crop elements, including 4 overlays (to show dark shadow), 1 container and 4 crop handles
 */
export function getCropHTML(): CreateElementData[] {
    const overlayHTML: CreateElementData = {
        tag: 'div',
        style: 'position:absolute;background-color:rgb(0,0,0,0.5);pointer-events:none',
        className: ImageEditElementClass.CropOverlay,
    };
    const containerHTML: CreateElementData = {
        tag: 'div',
        style: 'position:absolute;overflow:hidden;inset:0px;',
        className: ImageEditElementClass.CropContainer,
        children: [],
    };
 
    Eif (containerHTML) {
        XS_CROP.forEach(x =>
            YS_CROP.forEach(y => containerHTML.children?.push(getCropHTMLInternal(x, y)))
        );
    }
    return [containerHTML, overlayHTML, overlayHTML, overlayHTML, overlayHTML];
}
 
function getCropHTMLInternal(x: DNDDirectionX, y: DnDDirectionY): CreateElementData {
    const leftOrRight = x == 'w' ? 'left' : 'right';
    const topOrBottom = y == 'n' ? 'top' : 'bottom';
    const rotation = ROTATION[y + x];
 
    return {
        tag: 'div',
        className: ImageEditElementClass.CropHandle,
        style: `position:absolute;pointer-events:auto;cursor:${y}${x}-resize;${leftOrRight}:0;${topOrBottom}:0;width:${CROP_HANDLE_SIZE}px;height:${CROP_HANDLE_SIZE}px;transform:rotate(${rotation}deg)`,
        dataset: { x, y },
        children: getCropHandleHTML(),
    };
}
 
function getCropHandleHTML(): CreateElementData[] {
    const result: CreateElementData[] = [];
    [0, 1].forEach(layer =>
        [0, 1].forEach(dir => {
            result.push(getCropHandleHTMLInternal(layer, dir));
        })
    );
    return result;
}
 
function getCropHandleHTMLInternal(layer: number, dir: number): CreateElementData {
    const position =
        dir == 0
            ? `right:${layer}px;height:${CROP_HANDLE_WIDTH - layer * 2}px;`
            : `top:${layer}px;width:${CROP_HANDLE_WIDTH - layer * 2}px;`;
    const bgColor = layer == 0 ? 'white' : 'black';
 
    return {
        tag: 'div',
        style: `position:absolute;left:${layer}px;bottom:${layer}px;${position};background-color:${bgColor}`,
    };
}