MinsoftK

redownload

Showing 276 changed files with 0 additions and 4886 deletions
/* ICON BUTTON */
.tie-icon-add-button
&.icon-bubble .{prefix}-button[data-icontype="icon-bubble"] svg > use.active,
&.icon-heart .{prefix}-button[data-icontype="icon-heart"] svg > use.active,
&.icon-location .{prefix}-button[data-icontype="icon-location"] svg > use.active,
&.icon-polygon .{prefix}-button[data-icontype="icon-polygon"] svg > use.active,
&.icon-star .{prefix}-button[data-icontype="icon-star"] svg > use.active,
&.icon-star-2 .{prefix}-button[data-icontype="icon-star-2"] svg > use.active,
&.icon-arrow-3 .{prefix}-button[data-icontype="icon-arrow-3"] svg > use.active,
&.icon-arrow-2 .{prefix}-button[data-icontype="icon-arrow-2"] svg > use.active,
&.icon-arrow .{prefix}-button[data-icontype="icon-arrow"] svg > use.active,
&.icon-bubble .{prefix}-button[data-icontype="icon-bubble"] svg > use.active
display: block;
/* DRAW BUTTON */
.tie-draw-line-select-button
&.line .{prefix}-button.line svg > use.normal,
&.free .{prefix}-button.free svg > use.normal
display: none;
&.line .{prefix}-button.line svg > use.active,
&.free .{prefix}-button.free svg > use.active
display: block;
/* FLIP BUTTON */
.tie-flip-button
&.resetFlip .{prefix}-button.resetFlip,
&.flipX .{prefix}-button.flipX,
&.flipY .{prefix}-button.flipY
svg > use.normal
display: none;
svg > use.active
display: block;
/* MASK BUTTON */
.tie-mask-apply.apply.active .{prefix}-button.apply
label
color: #fff;
svg > use.active
display: block;
/* CROP BUTTON */
.tie-crop-button,
.tie-crop-preset-button
.{prefix}-button.apply
margin-right: 24px;
.{prefix}-button.preset.active svg > use.active
display: block;
.{prefix}-button.apply.active svg > use.active
display: block;
/* SHAPE BUTTON */
.tie-shape-button
&.rect .{prefix}-button.rect,
&.circle .{prefix}-button.circle,
&.triangle .{prefix}-button.triangle
svg > use.normal
display: none;
svg > use.active
display: block;
/* TEXT BUTTON */
.tie-text-effect-button
.{prefix}-button.active svg > use.active
display: block;
.tie-text-align-button
&.left .{prefix}-button.left svg > use.active,
&.center .{prefix}-button.center svg > use.active,
&.right .{prefix}-button.right svg > use.active
display: block;
.tie-mask-image-file,
.tie-icon-image-file
opacity: 0;
position: absolute;
width: 100%;
height: 100%;
border: 1px solid green;
cursor: inherit;
left: 0;
top: 0;
/* VIRTUAL CHECKBOX */
.{prefix}-container
.filter-color-item
display: inline-block;
.tui-image-editor-checkbox
display: block;
.{prefix}-checkbox-wrap
display: inline-block !important;
text-align: left;
.{prefix}-checkbox-wrap.fixed-width
width: 187px;
white-space: normal;
.{prefix}-checkbox
display: inline-block;
margin: 1px 0 1px 0;
input
width: 14px;
height: 14px;
opacity: 0;
> label > span
color: #fff;
height: 14px;
position: relative;
input + label:before,
> label > span:before
content: '';
position: absolute;
width: 14px;
height: 14px;
background-color: #fff;
top: 6px;
left: -19px;
display: inline-block;
margin: 0;
text-align: center;
font-size: 11px;
border: 0;
border-radius: 2px;
padding-top: 1px;
box-sizing: border-box;
input[type='checkbox']:checked + span:before
background-size: cover;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAMBJREFUKBWVkjEOwjAMRe2WgZW7IIHEDdhghhuwcQ42rlJugAQS54Cxa5cq1QM5TUpByZfS2j9+dlJVt/tX5ZxbS4ZU9VLkQvSHKTIGRaVJYFmKrBbTCJxE2UgCdDzMZDkHrOV6b95V0US6UmgKodujEZbJg0B0ZgEModO5lrY1TMQf1TpyJGBEjD+E2NPN7ukIUDiF/BfEXgRiGEw8NgkffYGYwCi808fpn/6OvfUfsDr/Vc1IfRf8sKnFVqeiVQfDu0tf/nWH9gAAAABJRU5ErkJggg==');
.{prefix}-selectlist-wrap
position: relative;
select
width: 100%;
height: 28px;
margin-top: 4px;
border: 0;
outline: 0;
border-radius: 0;
border: 1px solid #cbdbdb;
background-color: #fff;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding: 0 7px 0 10px;
.{prefix}-selectlist
display: none;
position: relative;
top: -1px;
border: 1px solid #ccc;
background-color: #fff;
border-top: 0px;
padding: 4px 0;
li
display: block;
text-align: left;
padding: 7px 10px;
font-family: 'Noto Sans', sans-serif;
li:hover
background-color: rgba(81, 92, 230, 0.05);
.{prefix}-selectlist-wrap:before
content: '';
position: absolute;
display: inline-block;
width: 14px;
height: 14px;
right: 5px;
top: 10px;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAHlJREFUKBVjYBgFOEOAEVkmPDxc89+/f6eAYjzI4kD2FyYmJrOVK1deh4kzwRggGiQBVJCELAZig8SQNYHEmEEEMrh69eo1HR0dfqCYJUickZGxf9WqVf3IakBsFBthklpaWmVA9mEQhrJhUoTp0NBQCRAmrHL4qgAAuu4cWZOZIGsAAAAASUVORK5CYII=');
background-size: cover;
.{prefix}-selectlist-wrap select::-ms-expand
display:none;
/* COLOR PICKER */
.{prefix}-container
div.tui-colorpicker-clearfix
width: 159px;
height: 28px;
border: 1px solid #d5d5d5;
border-radius: 2px;
background-color: #f5f5f5;
margin-top: 6px;
padding: 4px 7px 4px 7px;
.tui-colorpicker-palette-hex
width: 114px;
background-color: #f5f5f5;
border: 0;
font-size: 11px;
margin-top: 2px;
font-family: 'Noto Sans', sans-serif;
.tui-colorpicker-palette-hex[value='#ffffff'] + .tui-colorpicker-palette-preview,
.tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview
border: 1px solid #ccc;
.tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview
background-size: cover;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC');
.tui-colorpicker-palette-preview
border-radius: 100%;
float: left;
width: 17px;
height: 17px;
border: 0;
.color-picker-control
position: absolute;
display: none;
z-index: 99;
width: 192px;
background-color: #fff;
box-shadow: 0 3px 22px 6px rgba(0, 0, 0, .15);
padding: 16px;
border-radius: 2px;
.tui-colorpicker-palette-toggle-slider
display: none;
.tui-colorpicker-palette-button
border: 0;
border-radius: 100%;
margin: 2px;
background-size: cover;
font-size: 1px;
&[title='#ffffff']
border: 1px solid #ccc;
&[title='']
border: 1px solid #ccc;
.triangle
width: 0;
height: 0;
border-right: 7px solid transparent;
border-top: 8px solid #fff;
border-left: 7px solid transparent;
position: absolute;
bottom: -8px;
left: 84px;
.tui-colorpicker-container,
.tui-colorpicker-palette-container ul,
.tui-colorpicker-palette-container
width: 100%;
height: auto;
.filter-color-item
.color-picker-control label
font-color: #333;
font-weight: normal;
margin-right: 7pxleft
.tui-image-editor-checkbox
margin-top: 0;
input + label:before,
> label:before
left: -16px;
.color-picker
width: 100%;
height: auto;
.color-picker-value
width: 32px;
height: 32px;
border: 0px;
border-radius: 100%;
margin: auto;
margin-bottom: 1px;
&.transparent
border: 1px solid #cbcbcb;
background-size: cover;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC');
.color-picker-value + label
color: #fff;
.{prefix}-submenu svg > use
display: none;
.{prefix}-submenu svg > use.normal
display: block;
/* GRID VISUAL OF FLIP AND ROTATE MENU */
.{prefix}-container
.{prefix}-grid-visual
display: none;
position: absolute;
width: 100%;
height: 100%;
border: 1px solid rgba(255,255,255,0.7);
.{prefix}-main.{prefix}-menu-flip,
.{prefix}-main.{prefix}-menu-rotate
.tui-image-editor
transition: none;
.{prefix}-main.{prefix}-menu-flip .{prefix}-grid-visual,
.{prefix}-main.{prefix}-menu-rotate .{prefix}-grid-visual
display: block;
.{prefix}-grid-visual
table
width: 100%;
height: 100%;
border-collapse: collapse;
td
border: 1px solid rgba(255,255,255,0.3);
td.dot:before
content: '';
position: absolute;
box-sizing: border-box;
width: 10px;
height: 10px;
border: 0;
box-shadow: 0 0 1px 0 rgba(0,0,0,0.3);
border-radius: 100%;
background-color: #fff;
td.dot.left-top:before
top: -5px;
left: -5px;
td.dot.right-top:before
top: -5px;
right: -5px;
td.dot.left-bottom:before
bottom: -5px;
left: -5px;
td.dot.right-bottom:before
bottom: -5px;
right: -5px;
/* ICON */
.{prefix}-container
.tie-icon-add-button .{prefix}-button
min-width: 42px;
.svg_ic-menu
.svg_ic-helpmenu
width: 24px;
height: 24px;
.svg_ic-submenu
width: 32px;
height: 32px;
.svg_img-bi
width: 257px;
height: 26px;
.{prefix}-controls
svg > use
display: none;
.enabled svg:hover > use.hover
.normal svg:hover > use.hover
display: block;
.active svg:hover > use.hover
display: none;
svg > use.normal
display: block;
.active svg > use.active
display: block;
.enabled svg > use.enabled
display: block;
.active svg > use.normal,
.enabled svg > use.normal
display: none;
.help svg > use.disabled,
.help.enabled svg > use.normal
display: block;
.help.enabled svg > use.disabled
display: none;
.{prefix}-controls:hover
z-index: 3;
prefix = 'tui-image-editor'
@import 'main.styl'
@import 'gridtable.styl'
@import 'submenu.styl'
@import 'checkbox.styl'
@import 'range.styl'
@import 'position.styl'
@import 'icon.styl'
@import 'colorpicker.styl'
@import 'buttons.styl'
.{prefix}-container.top
&.{prefix}-top-optimization
.{prefix}-controls ul
text-align: right;
.{prefix}-controls-logo
display: none;
body > textarea
position: fixed !important;
+prefix-classes(prefix)
.-container
margin: 0;
padding: 0;
box-sizing: border-box;
min-height: 300px;
height: 100%;
position: relative;
background-color: #282828;
overflow: hidden;
letter-spacing: 0.3px;
div, ul, label, input, li
box-sizing: border-box;
margin: 0;
padding: 0;
-ms-user-select: none;
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
.-header
/* BUTTON AND LOGO */
min-width: 533px;
position: absolute;
background-color: #151515;
top: 0;
width: 100%;
.-header-buttons,
.-controls-buttons
float: right;
margin: 8px;
.-header-logo,
.-controls-logo
float: left;
width: 30%;
padding: 17px;
.-controls-logo,
.-controls-buttons
width: 270px;
height: 100%;
display: none;
.-header-buttons button,
.-header-buttons div,
.-controls-buttons button,
.-controls-buttons div
display: inline-block;
position: relative;
width: 120px;
height: 40px;
padding: 0;
line-height: 40px;
outline: none;
border-radius: 20px;
border: 1px solid #ddd;
font-family: 'Noto Sans', sans-serif;
font-size: 12px;
font-weight: bold;
cursor: pointer;
vertical-align: middle;
letter-spacing: 0.3px;
text-align: center;
.-download-btn
background-color: #fdba3b;
border-color: #fdba3b;
color: #fff;
.-load-btn
position: absolute;
left: 0;
right: 0;
display: inline-block;
top: 0;
bottom: 0;
width: 100%;
cursor: pointer;
opacity: 0;
.-main-container
position: absolute;
width: 100%;
top: 0;
bottom: 64px;
.-main
position: absolute;
text-align: center;
top: 64px;
bottom: 0;
right: 0;
left: 0;
.-wrap
position: absolute;
bottom: 0;
width: 100%;
overflow: auto;
.-size-wrap
display: table;
width: 100%;
height: 100%
.-align-wrap
display: table-cell;
vertical-align: middle;
.
position: relative;
display: inline-block;
/* BIG MENU */
.{prefix}-container
.{prefix}-menu
width: auto;
list-style: none;
padding: 0;
margin: 0 auto;
display: table-cell;
text-align: center;
vertical-align: middle;
white-space: nowrap;
> .{prefix}-item
position: relative;
display: inline-block;
border-radius: 2px;
padding: 7px 8px 3px 8px;
cursor: pointer;
margin: 0 4px;
> .{prefix}-item[tooltip-content]:hover
&:before
content: '';
position: absolute;
display: inline-block;
margin: 0 auto 0;
width: 0;
height: 0;
border-right: 7px solid transparent;
border-top: 7px solid #2f2f2f;
border-left: 7px solid transparent;
left: 13px;
top: -2px;
&:after
content: attr(tooltip-content);
position: absolute;
display: inline-block;
background-color: #2f2f2f;
color: #fff;
padding: 5px 8px;
font-size: 11px;
font-weight: lighter;
border-radius: 3px;
max-height: 23px;
top: -25px;
left: 0;
min-width: 24px;
> .{prefix}-item.active
background-color: #fff;
transition: all .3s ease;
.{prefix}-wrap
position: absolute;
/* POSITION LEFT */
.{prefix}-container
&.left
.{prefix}-menu
> .{prefix}-item[tooltip-content]
&:before
left: 28px;
top: 11px;
border-right: 7px solid #2f2f2f;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
&:after
top: 7px;
left: 42px;
white-space: nowrap;
.{prefix}-submenu
left: 0;
height: 100%;
width: 248px;
.{prefix}-main-container
left: 64px;
width: calc(100% - 64px);
height: 100%;
.{prefix}-controls
width: 64px;
height: 100%;
display: table;
/* POSITION LEFT & RIGHT */
.{prefix}-container
&.left, &.right
.{prefix}-menu
white-space: inherit;
.{prefix}-submenu
white-space: normal;
> div
vertical-align: middle;
.{prefix}-controls li
display: inline-block;
margin: 4px auto;
.{prefix}-icpartition
position: relative;
top: -7px;
width: 24px;
height: 1px;
.{prefix}-submenu
.{prefix}-partition
display: block;
width: 75%;
margin: auto;
> div
border-left: 0;
height:10px;
border-bottom: 1px solid #3c3c3c;
width: 100%;
margin: 0;
.{prefix}-submenu-align
margin-right: 0;
.{prefix}-submenu-item
li
margin-top: 15px;
.tui-colorpicker-clearfix li
margin-top: 0;
.{prefix}-checkbox-wrap.fixed-width
width: 182px;
white-space: normal;
.{prefix}-range-wrap.{prefix}-newline label.range
display: block;
text-align: left;
width: 75%;
margin: auto;
.{prefix}-range
width: 136px;
/* POSITION RIGIHT */
.{prefix}-container
&.right
.{prefix}-menu
> .{prefix}-item[tooltip-content]
&:before
left: -3px;
top: 11px;
border-left: 7px solid #2f2f2f;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
&:after
top: 7px;
left: unset;
right: 43px;
white-space: nowrap;
.{prefix}-submenu
right: 0;
height: 100%;
width: 248px;
.{prefix}-main-container
right: 64px;
width: calc(100% - 64px);
height: 100%;
.{prefix}-controls
right: 0;
width: 64px;
height: 100%;
display: table;
/* POSITION TOP & BOTTOM */
.{prefix}-container
&.top, &.bottom
.{prefix}-submenu
.{prefix}-partition.only-left-right
display: none;
/* POSITION BOTTOM */
.{prefix}-container
&.bottom .tui-image-editor-submenu > div
padding-bottom: 24px;
/* POSITION TOP */
.{prefix}-container
&.top
.color-picker-control .triangle
top: -8px;
border-right: 7px solid transparent;
border-top: 0px;
border-left: 7px solid transparent;
border-bottom: 8px solid #fff;
.{prefix}-size-wrap
height: 100%;
.{prefix}-main-container
bottom: 0;
.{prefix}-menu
> .{prefix}-item[tooltip-content]
&:before
left: 13px;
border-top: 0;
border-bottom: 7px solid #2f2f2f;
top: 33px;
&:after
top: 38px;
.{prefix}-submenu
top: 0;
bottom: auto;
> div
padding-top: 24px;
vertical-align: top;
.{prefix}-controls-logo
display: table-cell;
.{prefix}-controls-buttons
display: table-cell;
.{prefix}-main
top: 64px;
height: calc(100% - 64px);
.{prefix}-controls
top: 0;
bottom: inherit;
/* VIRTUAL RANGE */
.{prefix}-container
.{prefix}-virtual-range-bar
.{prefix}-virtual-range-subbar
.{prefix}-virtual-range-pointer
.{prefix}-disabled
backbround-color: red;
.{prefix}-range
position: relative;
top: 5px;
width: 166px;
height: 17px;
display: inline-block;
.{prefix}-virtual-range-bar
top: 7px;
position: absolute;
width: 100%;
height: 2px;
background-color: #666666;
.{prefix}-virtual-range-subbar
position: absolute;
height: 100%;
left: 0;
right: 0;
background-color: #d1d1d1;
.{prefix}-virtual-range-pointer
position: absolute;
cursor: pointer;
top: -5px;
left: 0;
width: 12px;
height: 12px;
background-color: #fff;
border-radius: 100%;
.{prefix}-range-wrap
display: inline-block;
margin-left: 4px;
&.short .tui-image-editor-range
width: 100px;
.color-picker-control
.{prefix}-range
width: 108px;
margin-left: 10px;
.{prefix}-virtual-range-pointer
background-color: #333;
.{prefix}-virtual-range-bar
background-color: #ccc;
.{prefix}-virtual-range-subbar
background-color: #606060;
.{prefix}-range-wrap.{prefix}-newline.short
margin-top: -2px;
margin-left: 19px;
label
color: #8e8e8e;
font-weight: normal;
.{prefix}-range-wrap label
vertical-align: baseline;
font-size: 11px;
margin-right: 7px;
color: #fff;
.{prefix}-range-value
cursor: default;
width: 40px;
height: 24px;
outline: none;
border-radius: 2px;
box-shadow: none;
border: 1px solid #d5d5d5;
text-align: center;
background-color: #1c1c1c;
color: #fff;
font-weight: lighter;
vertical-align: baseline;
font-family: 'Noto Sans', sans-serif;
margin-top: 21px;
margin-left: 4px;
.{prefix}-controls
position: absolute;
background-color: #151515;
width: 100%;
height: 64px;
display: table;
bottom: 0;
z-index: 2;
.{prefix}-icpartition
display: inline-block;
background-color: #282828;
width: 1px;
height: 24px;
\ No newline at end of file
/* SUBMENU */
.{prefix}-container
.{prefix}-submenu
display: none;
position: absolute;
bottom: 0;
width:100%;
height: 150px;
white-space: nowrap;
z-index: 2;
.{prefix}-button:hover svg > use.active
display: block;
.{prefix}-submenu-item
li
display: inline-block;
vertical-align: top;
.{prefix}-newline
display: block;
margin-top: 0;
.{prefix}-button
position: relative;
cursor: pointer;
display: inline-block;
font-weight: normal;
font-size: 11px;
margin: 0 9px 0 9px;
.{prefix}-button.preset
margin: 0 9px 20px 5px;
label > span
display: inline-block;
cursor: pointer;
padding-top: 5px;
font-family: "Noto Sans", sans-serif;
font-size: 11px;
.{prefix}-button.apply label,
.{prefix}-button.cancel label
vertical-align: 7px;
> div
display: none;
vertical-align: bottom;
.{prefix}-submenu-style
opacity: 0.95;
z-index: -1;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: block;
.{prefix}-partition > div
width: 1px;
height: 52px;
border-left: 1px solid #3c3c3c;
margin: 0 8px 0 8px;
.{prefix}-main.{prefix}-menu-filter .{prefix}-partition > div
height: 108px;
margin: 0 29px 0 0px;
.{prefix}-submenu-align
text-align: left;
margin-right: 30px;
label > span
width: 55px;
white-space: nowrap;
.{prefix}-submenu-align:first-child
margin-right: 0;
label > span
width: 70px;
.{prefix}-main.{prefix}-menu-crop .{prefix}-submenu > div.{prefix}-menu-crop,
.{prefix}-main.{prefix}-menu-flip .{prefix}-submenu > div.{prefix}-menu-flip,
.{prefix}-main.{prefix}-menu-rotate .{prefix}-submenu > div.{prefix}-menu-rotate,
.{prefix}-main.{prefix}-menu-shape .{prefix}-submenu > div.{prefix}-menu-shape,
.{prefix}-main.{prefix}-menu-text .{prefix}-submenu > div.{prefix}-menu-text,
.{prefix}-main.{prefix}-menu-mask .{prefix}-submenu > div.{prefix}-menu-mask,
.{prefix}-main.{prefix}-menu-icon .{prefix}-submenu > div.{prefix}-menu-icon,
.{prefix}-main.{prefix}-menu-draw .{prefix}-submenu > div.{prefix}-menu-draw,
.{prefix}-main.{prefix}-menu-filter .{prefix}-submenu > div.{prefix}-menu-filter
display: table-cell;
.{prefix}-main.{prefix}-menu-crop,
.{prefix}-main.{prefix}-menu-flip,
.{prefix}-main.{prefix}-menu-rotate,
.{prefix}-main.{prefix}-menu-shape,
.{prefix}-main.{prefix}-menu-text,
.{prefix}-main.{prefix}-menu-mask,
.{prefix}-main.{prefix}-menu-icon,
.{prefix}-main.{prefix}-menu-draw,
.{prefix}-main.{prefix}-menu-filter
.{prefix}-submenu
display: table;
import './js/polyfill';
import ImageEditor from './js/imageEditor';
import './css/index.styl';
// commands
import './js/command/addIcon';
import './js/command/addImageObject';
import './js/command/addObject';
import './js/command/addShape';
import './js/command/addText';
import './js/command/applyFilter';
import './js/command/changeIconColor';
import './js/command/changeShape';
import './js/command/changeText';
import './js/command/changeTextStyle';
import './js/command/clearObjects';
import './js/command/flip';
import './js/command/loadImage';
import './js/command/removeFilter';
import './js/command/removeObject';
import './js/command/resizeCanvasDimension';
import './js/command/rotate';
import './js/command/setObjectProperties';
import './js/command/setObjectPosition';
import './js/command/changeSelection';
module.exports = ImageEditor;
This diff is collapsed. Click to expand it.
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add an icon
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { componentNames, commandNames } from '../consts';
const { ICON } = componentNames;
const command = {
name: commandNames.ADD_ICON,
/**
* Add an icon
* @param {Graphics} graphics - Graphics instance
* @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
* @param {Object} options - Icon options
* @param {string} [options.fill] - Icon foreground color
* @param {string} [options.left] - Icon x position
* @param {string} [options.top] - Icon y position
* @returns {Promise}
*/
execute(graphics, type, options) {
const iconComp = graphics.getComponent(ICON);
return iconComp.add(type, options).then((objectProps) => {
this.undoData.object = graphics.getObject(objectProps.id);
return objectProps;
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
graphics.remove(this.undoData.object);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add an image object
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames } from '../consts';
const command = {
name: commandNames.ADD_IMAGE_OBJECT,
/**
* Add an image object
* @param {Graphics} graphics - Graphics instance
* @param {string} imgUrl - Image url to make object
* @returns {Promise}
*/
execute(graphics, imgUrl) {
return graphics.addImageObject(imgUrl).then((objectProps) => {
this.undoData.object = graphics.getObject(objectProps.id);
return objectProps;
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
graphics.remove(this.undoData.object);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add an object
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames, rejectMessages } from '../consts';
const command = {
name: commandNames.ADD_OBJECT,
/**
* Add an object
* @param {Graphics} graphics - Graphics instance
* @param {Object} object - Fabric object
* @returns {Promise}
*/
execute(graphics, object) {
return new Promise((resolve, reject) => {
if (!graphics.contains(object)) {
graphics.add(object);
resolve(object);
} else {
reject(rejectMessages.addedObject);
}
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @param {Object} object - Fabric object
* @returns {Promise}
*/
undo(graphics, object) {
return new Promise((resolve, reject) => {
if (graphics.contains(object)) {
graphics.remove(object);
resolve(object);
} else {
reject(rejectMessages.noObject);
}
});
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add a shape
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { componentNames, commandNames } from '../consts';
const { SHAPE } = componentNames;
const command = {
name: commandNames.ADD_SHAPE,
/**
* Add a shape
* @param {Graphics} graphics - Graphics instance
* @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle')
* @param {Object} options - Shape options
* @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
* @param {string} [options.stroke] - Shape outline color
* @param {number} [options.strokeWidth] - Shape outline width
* @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
* @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
* @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
* @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
* @param {number} [options.left] - Shape x position
* @param {number} [options.top] - Shape y position
* @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
* @returns {Promise}
*/
execute(graphics, type, options) {
const shapeComp = graphics.getComponent(SHAPE);
return shapeComp.add(type, options).then((objectProps) => {
this.undoData.object = graphics.getObject(objectProps.id);
return objectProps;
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
graphics.remove(this.undoData.object);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add a text object
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { componentNames, commandNames, rejectMessages } from '../consts';
const { TEXT } = componentNames;
const command = {
name: commandNames.ADD_TEXT,
/**
* Add a text object
* @param {Graphics} graphics - Graphics instance
* @param {string} text - Initial input text
* @param {Object} [options] Options for text styles
* @param {Object} [options.styles] Initial styles
* @param {string} [options.styles.fill] Color
* @param {string} [options.styles.fontFamily] Font type for text
* @param {number} [options.styles.fontSize] Size
* @param {string} [options.styles.fontStyle] Type of inclination (normal / italic)
* @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold)
* @param {string} [options.styles.textAlign] Type of text align (left / center / right)
* @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline)
* @param {{x: number, y: number}} [options.position] - Initial position
* @returns {Promise}
*/
execute(graphics, text, options) {
const textComp = graphics.getComponent(TEXT);
if (this.undoData.object) {
const undoObject = this.undoData.object;
return new Promise((resolve, reject) => {
if (!graphics.contains(undoObject)) {
graphics.add(undoObject);
resolve(undoObject);
} else {
reject(rejectMessages.redo);
}
});
}
return textComp.add(text, options).then((objectProps) => {
const { id } = objectProps;
const textObject = graphics.getObject(id);
this.undoData.object = textObject;
return objectProps;
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
graphics.remove(this.undoData.object);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Apply a filter into an image
*/
import snippet from 'tui-code-snippet';
import commandFactory from '../factory/command';
import { componentNames, rejectMessages, commandNames } from '../consts';
const { FILTER } = componentNames;
/**
* Chched data for undo
* @type {Object}
*/
let chchedUndoDataForSilent = null;
/**
* Make undoData
* @param {string} type - Filter type
* @param {Object} prevfilterOption - prev Filter options
* @param {Object} options - Filter options
* @returns {object} - undo data
*/
function makeUndoData(type, prevfilterOption, options) {
const undoData = {};
if (type === 'mask') {
undoData.object = options.mask;
}
undoData.options = prevfilterOption;
return undoData;
}
const command = {
name: commandNames.APPLY_FILTER,
/**
* Apply a filter into an image
* @param {Graphics} graphics - Graphics instance
* @param {string} type - Filter type
* @param {Object} options - Filter options
* @param {number} options.maskObjId - masking image object id
* @param {boolean} isSilent - is silent execution or not
* @returns {Promise}
*/
execute(graphics, type, options, isSilent) {
const filterComp = graphics.getComponent(FILTER);
if (type === 'mask') {
const maskObj = graphics.getObject(options.maskObjId);
if (!(maskObj && maskObj.isType('image'))) {
return Promise.reject(rejectMessages.invalidParameters);
}
snippet.extend(options, { mask: maskObj });
graphics.remove(options.mask);
}
if (!this.isRedo) {
const prevfilterOption = filterComp.getOptions(type);
const undoData = makeUndoData(type, prevfilterOption, options);
chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
}
return filterComp.add(type, options);
},
/**
* @param {Graphics} graphics - Graphics instance
* @param {string} type - Filter type
* @returns {Promise}
*/
undo(graphics, type) {
const filterComp = graphics.getComponent(FILTER);
if (type === 'mask') {
const mask = this.undoData.object;
graphics.add(mask);
graphics.setActiveObject(mask);
return filterComp.remove(type);
}
// options changed case
if (this.undoData.options) {
return filterComp.add(type, this.undoData.options);
}
// filter added case
return filterComp.remove(type);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Change icon color
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { componentNames, rejectMessages, commandNames } from '../consts';
const { ICON } = componentNames;
const command = {
name: commandNames.CHANGE_ICON_COLOR,
/**
* Change icon color
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @param {string} color - Color for icon
* @returns {Promise}
*/
execute(graphics, id, color) {
return new Promise((resolve, reject) => {
const iconComp = graphics.getComponent(ICON);
const targetObj = graphics.getObject(id);
if (!targetObj) {
reject(rejectMessages.noObject);
}
this.undoData.object = targetObj;
this.undoData.color = iconComp.getColor(targetObj);
iconComp.setColor(color, targetObj);
resolve();
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const iconComp = graphics.getComponent(ICON);
const { object: icon, color } = this.undoData;
iconComp.setColor(color, icon);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN. FE Development Team <dl_javascript@nhn.com>
* @fileoverview change selection
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames } from '../consts';
import { getCachedUndoDataForDimension } from '../helper/selectionModifyHelper';
const command = {
name: commandNames.CHANGE_SELECTION,
execute(graphics, props) {
if (this.isRedo) {
props.forEach((prop) => {
graphics.setObjectProperties(prop.id, prop);
});
} else {
this.undoData = getCachedUndoDataForDimension();
}
return Promise.resolve();
},
undo(graphics) {
this.undoData.forEach((datum) => {
graphics.setObjectProperties(datum.id, datum);
});
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview change a shape
*/
import snippet from 'tui-code-snippet';
import { Promise } from '../util';
import commandFactory from '../factory/command';
import { componentNames, rejectMessages, commandNames } from '../consts';
const { SHAPE } = componentNames;
/**
* Chched data for undo
* @type {Object}
*/
let chchedUndoDataForSilent = null;
/**
* Make undoData
* @param {object} options - shape options
* @param {Component} targetObj - shape component
* @returns {object} - undo data
*/
function makeUndoData(options, targetObj) {
const undoData = {
object: targetObj,
options: {},
};
snippet.forEachOwnProperties(options, (value, key) => {
undoData.options[key] = targetObj[key];
});
return undoData;
}
const command = {
name: commandNames.CHANGE_SHAPE,
/**
* Change a shape
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @param {Object} options - Shape options
* @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent')
* @param {string} [options.stroke] - Shape outline color
* @param {number} [options.strokeWidth] - Shape outline width
* @param {number} [options.width] - Width value (When type option is 'rect', this options can use)
* @param {number} [options.height] - Height value (When type option is 'rect', this options can use)
* @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use)
* @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use)
* @param {number} [options.left] - Shape x position
* @param {number} [options.top] - Shape y position
* @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not
* @param {boolean} isSilent - is silent execution or not
* @returns {Promise}
*/
execute(graphics, id, options, isSilent) {
const shapeComp = graphics.getComponent(SHAPE);
const targetObj = graphics.getObject(id);
if (!targetObj) {
return Promise.reject(rejectMessages.noObject);
}
if (!this.isRedo) {
const undoData = makeUndoData(options, targetObj);
chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
}
return shapeComp.change(targetObj, options);
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const shapeComp = graphics.getComponent(SHAPE);
const { object: shape, options } = this.undoData;
return shapeComp.change(shape, options);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Change a text
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { componentNames, rejectMessages, commandNames } from '../consts';
const { TEXT } = componentNames;
const command = {
name: commandNames.CHANGE_TEXT,
/**
* Change a text
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @param {string} text - Changing text
* @returns {Promise}
*/
execute(graphics, id, text) {
const textComp = graphics.getComponent(TEXT);
const targetObj = graphics.getObject(id);
if (!targetObj) {
return Promise.reject(rejectMessages.noObject);
}
this.undoData.object = targetObj;
this.undoData.text = textComp.getText(targetObj);
return textComp.change(targetObj, text);
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const textComp = graphics.getComponent(TEXT);
const { object: textObj, text } = this.undoData;
return textComp.change(textObj, text);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Change text styles
*/
import snippet from 'tui-code-snippet';
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { componentNames, rejectMessages, commandNames } from '../consts';
const { TEXT } = componentNames;
/**
* Chched data for undo
* @type {Object}
*/
let chchedUndoDataForSilent = null;
/**
* Make undoData
* @param {object} styles - text styles
* @param {Component} targetObj - text component
* @returns {object} - undo data
*/
function makeUndoData(styles, targetObj) {
const undoData = {
object: targetObj,
styles: {},
};
snippet.forEachOwnProperties(styles, (value, key) => {
const undoValue = targetObj[key];
undoData.styles[key] = undoValue;
});
return undoData;
}
const command = {
name: commandNames.CHANGE_TEXT_STYLE,
/**
* Change text styles
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @param {Object} styles - text styles
* @param {string} [styles.fill] Color
* @param {string} [styles.fontFamily] Font type for text
* @param {number} [styles.fontSize] Size
* @param {string} [styles.fontStyle] Type of inclination (normal / italic)
* @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold)
* @param {string} [styles.textAlign] Type of text align (left / center / right)
* @param {string} [styles.textDecoration] Type of line (underline / line-through / overline)
* @param {boolean} isSilent - is silent execution or not
* @returns {Promise}
*/
execute(graphics, id, styles, isSilent) {
const textComp = graphics.getComponent(TEXT);
const targetObj = graphics.getObject(id);
if (!targetObj) {
return Promise.reject(rejectMessages.noObject);
}
if (!this.isRedo) {
const undoData = makeUndoData(styles, targetObj);
chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
}
return textComp.setStyle(targetObj, styles);
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const textComp = graphics.getComponent(TEXT);
const { object: textObj, styles } = this.undoData;
return textComp.setStyle(textObj, styles);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Clear all objects
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames } from '../consts';
const command = {
name: commandNames.CLEAR_OBJECTS,
/**
* Clear all objects without background (main) image
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
execute(graphics) {
return new Promise((resolve) => {
this.undoData.objects = graphics.removeAll();
resolve();
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
* @ignore
*/
undo(graphics) {
graphics.add(this.undoData.objects);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Flip an image
*/
import commandFactory from '../factory/command';
import { componentNames, commandNames } from '../consts';
const { FLIP } = componentNames;
const command = {
name: commandNames.FLIP_IMAGE,
/**
* flip an image
* @param {Graphics} graphics - Graphics instance
* @param {string} type - 'flipX' or 'flipY' or 'reset'
* @returns {Promise}
*/
execute(graphics, type) {
const flipComp = graphics.getComponent(FLIP);
this.undoData.setting = flipComp.getCurrentSetting();
return flipComp[type]();
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const flipComp = graphics.getComponent(FLIP);
return flipComp.set(this.undoData.setting);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Load a background (main) image
*/
import commandFactory from '../factory/command';
import { componentNames, commandNames } from '../consts';
const { IMAGE_LOADER } = componentNames;
const command = {
name: commandNames.LOAD_IMAGE,
/**
* Load a background (main) image
* @param {Graphics} graphics - Graphics instance
* @param {string} imageName - Image name
* @param {string} imgUrl - Image Url
* @returns {Promise}
*/
execute(graphics, imageName, imgUrl) {
const loader = graphics.getComponent(IMAGE_LOADER);
const prevImage = loader.getCanvasImage();
const prevImageWidth = prevImage ? prevImage.width : 0;
const prevImageHeight = prevImage ? prevImage.height : 0;
const objects = graphics.removeAll(true).filter((objectItem) => objectItem.type !== 'cropzone');
objects.forEach((objectItem) => {
objectItem.evented = true;
});
this.undoData = {
name: loader.getImageName(),
image: prevImage,
objects,
};
return loader.load(imageName, imgUrl).then((newImage) => ({
oldWidth: prevImageWidth,
oldHeight: prevImageHeight,
newWidth: newImage.width,
newHeight: newImage.height,
}));
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const loader = graphics.getComponent(IMAGE_LOADER);
const { objects, name, image } = this.undoData;
graphics.removeAll(true);
graphics.add(objects);
return loader.load(name, image);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Remove a filter from an image
*/
import commandFactory from '../factory/command';
import { componentNames, commandNames } from '../consts';
const { FILTER } = componentNames;
const command = {
name: commandNames.REMOVE_FILTER,
/**
* Remove a filter from an image
* @param {Graphics} graphics - Graphics instance
* @param {string} type - Filter type
* @returns {Promise}
*/
execute(graphics, type) {
const filterComp = graphics.getComponent(FILTER);
this.undoData.options = filterComp.getOptions(type);
return filterComp.remove(type);
},
/**
* @param {Graphics} graphics - Graphics instance
* @param {string} type - Filter type
* @returns {Promise}
*/
undo(graphics, type) {
const filterComp = graphics.getComponent(FILTER);
const { options } = this.undoData;
return filterComp.add(type, options);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Remove an object
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames, rejectMessages } from '../consts';
const command = {
name: commandNames.REMOVE_OBJECT,
/**
* Remove an object
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @returns {Promise}
*/
execute(graphics, id) {
return new Promise((resolve, reject) => {
this.undoData.objects = graphics.removeObjectById(id);
if (this.undoData.objects.length) {
resolve();
} else {
reject(rejectMessages.noObject);
}
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
graphics.add(this.undoData.objects);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Resize a canvas
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames } from '../consts';
const command = {
name: commandNames.RESIZE_CANVAS_DIMENSION,
/**
* resize the canvas with given dimension
* @param {Graphics} graphics - Graphics instance
* @param {{width: number, height: number}} dimension - Max width & height
* @returns {Promise}
*/
execute(graphics, dimension) {
return new Promise((resolve) => {
this.undoData.size = {
width: graphics.cssMaxWidth,
height: graphics.cssMaxHeight,
};
graphics.setCssMaxDimension(dimension);
graphics.adjustCanvasDimension();
resolve();
});
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
graphics.setCssMaxDimension(this.undoData.size);
graphics.adjustCanvasDimension();
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Rotate an image
*/
import commandFactory from '../factory/command';
import { componentNames, commandNames } from '../consts';
const { ROTATION } = componentNames;
/**
* Chched data for undo
* @type {Object}
*/
let chchedUndoDataForSilent = null;
/**
* Make undo data
* @param {Component} rotationComp - rotation component
* @returns {object} - undodata
*/
function makeUndoData(rotationComp) {
return {
angle: rotationComp.getCurrentAngle(),
};
}
const command = {
name: commandNames.ROTATE_IMAGE,
/**
* Rotate an image
* @param {Graphics} graphics - Graphics instance
* @param {string} type - 'rotate' or 'setAngle'
* @param {number} angle - angle value (degree)
* @param {boolean} isSilent - is silent execution or not
* @returns {Promise}
*/
execute(graphics, type, angle, isSilent) {
const rotationComp = graphics.getComponent(ROTATION);
if (!this.isRedo) {
const undoData = makeUndoData(rotationComp);
chchedUndoDataForSilent = this.setUndoData(undoData, chchedUndoDataForSilent, isSilent);
}
return rotationComp[type](angle);
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const rotationComp = graphics.getComponent(ROTATION);
const [, type, angle] = this.args;
if (type === 'setAngle') {
return rotationComp[type](this.undoData.angle);
}
return rotationComp.rotate(-angle);
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Set object properties
*/
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames, rejectMessages } from '../consts';
const command = {
name: commandNames.SET_OBJECT_POSITION,
/**
* Set object properties
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @param {Object} posInfo - position object
* @param {number} posInfo.x - x position
* @param {number} posInfo.y - y position
* @param {string} posInfo.originX - can be 'left', 'center', 'right'
* @param {string} posInfo.originY - can be 'top', 'center', 'bottom'
* @returns {Promise}
*/
execute(graphics, id, posInfo) {
const targetObj = graphics.getObject(id);
if (!targetObj) {
return Promise.reject(rejectMessages.noObject);
}
this.undoData.objectId = id;
this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']);
graphics.setObjectPosition(id, posInfo);
graphics.renderAll();
return Promise.resolve();
},
/**
* @param {Graphics} graphics - Graphics instance
* @returns {Promise}
*/
undo(graphics) {
const { objectId, props } = this.undoData;
graphics.setObjectProperties(objectId, props);
graphics.renderAll();
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Set object properties
*/
import snippet from 'tui-code-snippet';
import commandFactory from '../factory/command';
import { Promise } from '../util';
import { commandNames, rejectMessages } from '../consts';
const command = {
name: commandNames.SET_OBJECT_PROPERTIES,
/**
* Set object properties
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @param {Object} props - properties
* @param {string} [props.fill] Color
* @param {string} [props.fontFamily] Font type for text
* @param {number} [props.fontSize] Size
* @param {string} [props.fontStyle] Type of inclination (normal / italic)
* @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold)
* @param {string} [props.textAlign] Type of text align (left / center / right)
* @param {string} [props.textDecoration] Type of line (underline / line-through / overline)
* @returns {Promise}
*/
execute(graphics, id, props) {
const targetObj = graphics.getObject(id);
if (!targetObj) {
return Promise.reject(rejectMessages.noObject);
}
this.undoData.props = {};
snippet.forEachOwnProperties(props, (value, key) => {
this.undoData.props[key] = targetObj[key];
});
graphics.setObjectProperties(id, props);
return Promise.resolve();
},
/**
* @param {Graphics} graphics - Graphics instance
* @param {number} id - object id
* @returns {Promise}
*/
undo(graphics, id) {
const { props } = this.undoData;
graphics.setObjectProperties(id, props);
return Promise.resolve();
},
};
commandFactory.register(command);
export default command;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Image crop module (start cropping, end cropping)
*/
import snippet from 'tui-code-snippet';
import fabric from 'fabric';
import Component from '../interface/component';
import Cropzone from '../extension/cropzone';
import { keyCodes, componentNames, CROPZONE_DEFAULT_OPTIONS } from '../consts';
import { clamp, fixFloatingPoint } from '../util';
const MOUSE_MOVE_THRESHOLD = 10;
const DEFAULT_OPTION = {
presetRatio: null,
top: -10,
left: -10,
height: 1,
width: 1,
};
/**
* Cropper components
* @param {Graphics} graphics - Graphics instance
* @extends {Component}
* @class Cropper
* @ignore
*/
class Cropper extends Component {
constructor(graphics) {
super(componentNames.CROPPER, graphics);
/**
* Cropzone
* @type {Cropzone}
* @private
*/
this._cropzone = null;
/**
* StartX of Cropzone
* @type {number}
* @private
*/
this._startX = null;
/**
* StartY of Cropzone
* @type {number}
* @private
*/
this._startY = null;
/**
* State whether shortcut key is pressed or not
* @type {boolean}
* @private
*/
this._withShiftKey = false;
/**
* Listeners
* @type {object.<string, function>}
* @private
*/
this._listeners = {
keydown: this._onKeyDown.bind(this),
keyup: this._onKeyUp.bind(this),
mousedown: this._onFabricMouseDown.bind(this),
mousemove: this._onFabricMouseMove.bind(this),
mouseup: this._onFabricMouseUp.bind(this),
};
}
/**
* Start cropping
*/
start() {
if (this._cropzone) {
return;
}
const canvas = this.getCanvas();
canvas.forEachObject((obj) => {
// {@link http://fabricjs.com/docs/fabric.Object.html#evented}
obj.evented = false;
});
this._cropzone = new Cropzone(
canvas,
snippet.extend(
{
left: 0,
top: 0,
width: 0.5,
height: 0.5,
strokeWidth: 0, // {@link https://github.com/kangax/fabric.js/issues/2860}
cornerSize: 10,
cornerColor: 'black',
fill: 'transparent',
},
CROPZONE_DEFAULT_OPTIONS,
this.graphics.cropSelectionStyle
)
);
canvas.discardActiveObject();
canvas.add(this._cropzone);
canvas.on('mouse:down', this._listeners.mousedown);
canvas.selection = false;
canvas.defaultCursor = 'crosshair';
fabric.util.addListener(document, 'keydown', this._listeners.keydown);
fabric.util.addListener(document, 'keyup', this._listeners.keyup);
}
/**
* End cropping
*/
end() {
const canvas = this.getCanvas();
const cropzone = this._cropzone;
if (!cropzone) {
return;
}
canvas.remove(cropzone);
canvas.selection = true;
canvas.defaultCursor = 'default';
canvas.off('mouse:down', this._listeners.mousedown);
canvas.forEachObject((obj) => {
obj.evented = true;
});
this._cropzone = null;
fabric.util.removeListener(document, 'keydown', this._listeners.keydown);
fabric.util.removeListener(document, 'keyup', this._listeners.keyup);
}
/**
* Change cropzone visible
* @param {boolean} visible - cropzone visible state
*/
changeVisibility(visible) {
if (this._cropzone) {
this._cropzone.set({ visible });
}
}
/**
* onMousedown handler in fabric canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
* @private
*/
_onFabricMouseDown(fEvent) {
const canvas = this.getCanvas();
if (fEvent.target) {
return;
}
canvas.selection = false;
const coord = canvas.getPointer(fEvent.e);
this._startX = coord.x;
this._startY = coord.y;
canvas.on({
'mouse:move': this._listeners.mousemove,
'mouse:up': this._listeners.mouseup,
});
}
/**
* onMousemove handler in fabric canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event
* @private
*/
_onFabricMouseMove(fEvent) {
const canvas = this.getCanvas();
const pointer = canvas.getPointer(fEvent.e);
const { x, y } = pointer;
const cropzone = this._cropzone;
if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) {
canvas.remove(cropzone);
cropzone.set(this._calcRectDimensionFromPoint(x, y));
canvas.add(cropzone);
canvas.setActiveObject(cropzone);
}
}
/**
* Get rect dimension setting from Canvas-Mouse-Position(x, y)
* @param {number} x - Canvas-Mouse-Position x
* @param {number} y - Canvas-Mouse-Position Y
* @returns {{left: number, top: number, width: number, height: number}}
* @private
*/
_calcRectDimensionFromPoint(x, y) {
const canvas = this.getCanvas();
const canvasWidth = canvas.getWidth();
const canvasHeight = canvas.getHeight();
const startX = this._startX;
const startY = this._startY;
let left = clamp(x, 0, startX);
let top = clamp(y, 0, startY);
let width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left
let height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top
if (this._withShiftKey) {
// make fixed ratio cropzone
if (width > height) {
height = width;
} else if (height > width) {
width = height;
}
if (startX >= x) {
left = startX - width;
}
if (startY >= y) {
top = startY - height;
}
}
return {
left,
top,
width,
height,
};
}
/**
* onMouseup handler in fabric canvas
* @private
*/
_onFabricMouseUp() {
const cropzone = this._cropzone;
const listeners = this._listeners;
const canvas = this.getCanvas();
canvas.setActiveObject(cropzone);
canvas.off({
'mouse:move': listeners.mousemove,
'mouse:up': listeners.mouseup,
});
}
/**
* Get cropped image data
* @param {Object} cropRect cropzone rect
* @param {Number} cropRect.left left position
* @param {Number} cropRect.top top position
* @param {Number} cropRect.width width
* @param {Number} cropRect.height height
* @returns {?{imageName: string, url: string}} cropped Image data
*/
getCroppedImageData(cropRect) {
const canvas = this.getCanvas();
const containsCropzone = canvas.contains(this._cropzone);
if (!cropRect) {
return null;
}
if (containsCropzone) {
canvas.remove(this._cropzone);
}
const imageData = {
imageName: this.getImageName(),
url: canvas.toDataURL(cropRect),
};
if (containsCropzone) {
canvas.add(this._cropzone);
}
return imageData;
}
/**
* Get cropped rect
* @returns {Object} rect
*/
getCropzoneRect() {
const cropzone = this._cropzone;
if (!cropzone.isValid()) {
return null;
}
return {
left: cropzone.left,
top: cropzone.top,
width: cropzone.width,
height: cropzone.height,
};
}
/**
* Set a cropzone square
* @param {number} [presetRatio] - preset ratio
*/
setCropzoneRect(presetRatio) {
const canvas = this.getCanvas();
const cropzone = this._cropzone;
canvas.discardActiveObject();
canvas.selection = false;
canvas.remove(cropzone);
cropzone.set(presetRatio ? this._getPresetPropertiesForCropSize(presetRatio) : DEFAULT_OPTION);
canvas.add(cropzone);
canvas.selection = true;
if (presetRatio) {
canvas.setActiveObject(cropzone);
}
}
/**
* get a cropzone square info
* @param {number} presetRatio - preset ratio
* @returns {{presetRatio: number, left: number, top: number, width: number, height: number}}
* @private
*/
_getPresetPropertiesForCropSize(presetRatio) {
const canvas = this.getCanvas();
const originalWidth = canvas.getWidth();
const originalHeight = canvas.getHeight();
const standardSize = originalWidth >= originalHeight ? originalWidth : originalHeight;
const getScale = (value, orignalValue) => (value > orignalValue ? orignalValue / value : 1);
let width = standardSize * presetRatio;
let height = standardSize;
const scaleWidth = getScale(width, originalWidth);
[width, height] = snippet.map([width, height], (sizeValue) => sizeValue * scaleWidth);
const scaleHeight = getScale(height, originalHeight);
[width, height] = snippet.map([width, height], (sizeValue) =>
fixFloatingPoint(sizeValue * scaleHeight)
);
return {
presetRatio,
top: (originalHeight - height) / 2,
left: (originalWidth - width) / 2,
width,
height,
};
}
/**
* Keydown event handler
* @param {KeyboardEvent} e - Event object
* @private
*/
_onKeyDown(e) {
if (e.keyCode === keyCodes.SHIFT) {
this._withShiftKey = true;
}
}
/**
* Keyup event handler
* @param {KeyboardEvent} e - Event object
* @private
*/
_onKeyUp(e) {
if (e.keyCode === keyCodes.SHIFT) {
this._withShiftKey = false;
}
}
}
export default Cropper;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add filter module
*/
import { isUndefined, extend, forEach, filter } from 'tui-code-snippet';
import { Promise } from '../util';
import fabric from 'fabric';
import Component from '../interface/component';
import Mask from '../extension/mask';
import { rejectMessages, componentNames } from '../consts';
import Sharpen from '../extension/sharpen';
import Emboss from '../extension/emboss';
import ColorFilter from '../extension/colorFilter';
const { filters } = fabric.Image;
filters.Mask = Mask;
filters.Sharpen = Sharpen;
filters.Emboss = Emboss;
filters.ColorFilter = ColorFilter;
/**
* Filter
* @class Filter
* @param {Graphics} graphics - Graphics instance
* @extends {Component}
* @ignore
*/
class Filter extends Component {
constructor(graphics) {
super(componentNames.FILTER, graphics);
}
/**
* Add filter to source image (a specific filter is added on fabric.js)
* @param {string} type - Filter type
* @param {Object} [options] - Options of filter
* @returns {Promise}
*/
add(type, options) {
return new Promise((resolve, reject) => {
const sourceImg = this._getSourceImage();
const canvas = this.getCanvas();
let imgFilter = this._getFilter(sourceImg, type);
if (!imgFilter) {
imgFilter = this._createFilter(sourceImg, type, options);
}
if (!imgFilter) {
reject(rejectMessages.invalidParameters);
}
this._changeFilterValues(imgFilter, options);
this._apply(sourceImg, () => {
canvas.renderAll();
resolve({
type,
action: 'add',
options,
});
});
});
}
/**
* Remove filter to source image
* @param {string} type - Filter type
* @returns {Promise}
*/
remove(type) {
return new Promise((resolve, reject) => {
const sourceImg = this._getSourceImage();
const canvas = this.getCanvas();
const options = this.getOptions(type);
if (!sourceImg.filters.length) {
reject(rejectMessages.unsupportedOperation);
}
this._removeFilter(sourceImg, type);
this._apply(sourceImg, () => {
canvas.renderAll();
resolve({
type,
action: 'remove',
options,
});
});
});
}
/**
* Whether this has the filter or not
* @param {string} type - Filter type
* @returns {boolean} true if it has the filter
*/
hasFilter(type) {
return !!this._getFilter(this._getSourceImage(), type);
}
/**
* Get a filter options
* @param {string} type - Filter type
* @returns {Object} filter options or null if there is no that filter
*/
getOptions(type) {
const sourceImg = this._getSourceImage();
const imgFilter = this._getFilter(sourceImg, type);
if (!imgFilter) {
return null;
}
return extend({}, imgFilter.options);
}
/**
* Change filter values
* @param {Object} imgFilter object of filter
* @param {Object} options object
* @private
*/
_changeFilterValues(imgFilter, options) {
forEach(options, (value, key) => {
if (!isUndefined(imgFilter[key])) {
imgFilter[key] = value;
}
});
forEach(imgFilter.options, (value, key) => {
if (!isUndefined(options[key])) {
imgFilter.options[key] = options[key];
}
});
}
/**
* Apply filter
* @param {fabric.Image} sourceImg - Source image to apply filter
* @param {function} callback - Executed function after applying filter
* @private
*/
_apply(sourceImg, callback) {
sourceImg.filters.push();
const result = sourceImg.applyFilters();
if (result) {
callback();
}
}
/**
* Get source image on canvas
* @returns {fabric.Image} Current source image on canvas
* @private
*/
_getSourceImage() {
return this.getCanvasImage();
}
/**
* Create filter instance
* @param {fabric.Image} sourceImg - Source image to apply filter
* @param {string} type - Filter type
* @param {Object} [options] - Options of filter
* @returns {Object} Fabric object of filter
* @private
*/
_createFilter(sourceImg, type, options) {
let filterObj;
// capitalize first letter for matching with fabric image filter name
const fabricType = this._getFabricFilterType(type);
const ImageFilter = fabric.Image.filters[fabricType];
if (ImageFilter) {
filterObj = new ImageFilter(options);
filterObj.options = options;
sourceImg.filters.push(filterObj);
}
return filterObj;
}
/**
* Get applied filter instance
* @param {fabric.Image} sourceImg - Source image to apply filter
* @param {string} type - Filter type
* @returns {Object} Fabric object of filter
* @private
*/
_getFilter(sourceImg, type) {
let imgFilter = null;
if (sourceImg) {
const fabricType = this._getFabricFilterType(type);
const { length } = sourceImg.filters;
let item, i;
for (i = 0; i < length; i += 1) {
item = sourceImg.filters[i];
if (item.type === fabricType) {
imgFilter = item;
break;
}
}
}
return imgFilter;
}
/**
* Remove applied filter instance
* @param {fabric.Image} sourceImg - Source image to apply filter
* @param {string} type - Filter type
* @private
*/
_removeFilter(sourceImg, type) {
const fabricType = this._getFabricFilterType(type);
sourceImg.filters = filter(sourceImg.filters, (value) => value.type !== fabricType);
}
/**
* Change filter class name to fabric's, especially capitalizing first letter
* @param {string} type - Filter type
* @example
* 'grayscale' -> 'Grayscale'
* @returns {string} Fabric filter class name
*/
_getFabricFilterType(type) {
return type.charAt(0).toUpperCase() + type.slice(1);
}
}
export default Filter;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Image flip module
*/
import snippet from 'tui-code-snippet';
import { Promise } from '../util';
import Component from '../interface/component';
import { componentNames, rejectMessages } from '../consts';
/**
* Flip
* @class Flip
* @param {Graphics} graphics - Graphics instance
* @extends {Component}
* @ignore
*/
class Flip extends Component {
constructor(graphics) {
super(componentNames.FLIP, graphics);
}
/**
* Get current flip settings
* @returns {{flipX: Boolean, flipY: Boolean}}
*/
getCurrentSetting() {
const canvasImage = this.getCanvasImage();
return {
flipX: canvasImage.flipX,
flipY: canvasImage.flipY,
};
}
/**
* Set flipX, flipY
* @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting
* @returns {Promise}
*/
set(newSetting) {
const setting = this.getCurrentSetting();
const isChangingFlipX = setting.flipX !== newSetting.flipX;
const isChangingFlipY = setting.flipY !== newSetting.flipY;
if (!isChangingFlipX && !isChangingFlipY) {
return Promise.reject(rejectMessages.flip);
}
snippet.extend(setting, newSetting);
this.setImageProperties(setting, true);
this._invertAngle(isChangingFlipX, isChangingFlipY);
this._flipObjects(isChangingFlipX, isChangingFlipY);
return Promise.resolve({
flipX: setting.flipX,
flipY: setting.flipY,
angle: this.getCanvasImage().angle,
});
}
/**
* Invert image angle for flip
* @param {boolean} isChangingFlipX - Change flipX
* @param {boolean} isChangingFlipY - Change flipY
*/
_invertAngle(isChangingFlipX, isChangingFlipY) {
const canvasImage = this.getCanvasImage();
let { angle } = canvasImage;
if (isChangingFlipX) {
angle *= -1;
}
if (isChangingFlipY) {
angle *= -1;
}
canvasImage.rotate(parseFloat(angle)).setCoords(); // parseFloat for -0 to 0
}
/**
* Flip objects
* @param {boolean} isChangingFlipX - Change flipX
* @param {boolean} isChangingFlipY - Change flipY
* @private
*/
_flipObjects(isChangingFlipX, isChangingFlipY) {
const canvas = this.getCanvas();
if (isChangingFlipX) {
canvas.forEachObject((obj) => {
obj
.set({
angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
flipX: !obj.flipX,
left: canvas.width - obj.left,
})
.setCoords();
});
}
if (isChangingFlipY) {
canvas.forEachObject((obj) => {
obj
.set({
angle: parseFloat(obj.angle * -1), // parseFloat for -0 to 0
flipY: !obj.flipY,
top: canvas.height - obj.top,
})
.setCoords();
});
}
canvas.renderAll();
}
/**
* Reset flip settings
* @returns {Promise}
*/
reset() {
return this.set({
flipX: false,
flipY: false,
});
}
/**
* Flip x
* @returns {Promise}
*/
flipX() {
const current = this.getCurrentSetting();
return this.set({
flipX: !current.flipX,
flipY: current.flipY,
});
}
/**
* Flip y
* @returns {Promise}
*/
flipY() {
const current = this.getCurrentSetting();
return this.set({
flipX: current.flipX,
flipY: !current.flipY,
});
}
}
export default Flip;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Free drawing module, Set brush
*/
import fabric from 'fabric';
import Component from '../interface/component';
import { componentNames } from '../consts';
/**
* FreeDrawing
* @class FreeDrawing
* @param {Graphics} graphics - Graphics instance
* @extends {Component}
* @ignore
*/
class FreeDrawing extends Component {
constructor(graphics) {
super(componentNames.FREE_DRAWING, graphics);
/**
* Brush width
* @type {number}
*/
this.width = 12;
/**
* fabric.Color instance for brush color
* @type {fabric.Color}
*/
this.oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
}
/**
* Start free drawing mode
* @param {{width: ?number, color: ?string}} [setting] - Brush width & color
*/
start(setting) {
const canvas = this.getCanvas();
canvas.isDrawingMode = true;
this.setBrush(setting);
}
/**
* Set brush
* @param {{width: ?number, color: ?string}} [setting] - Brush width & color
*/
setBrush(setting) {
const brush = this.getCanvas().freeDrawingBrush;
setting = setting || {};
this.width = setting.width || this.width;
if (setting.color) {
this.oColor = new fabric.Color(setting.color);
}
brush.width = this.width;
brush.color = this.oColor.toRgba();
}
/**
* End free drawing mode
*/
end() {
const canvas = this.getCanvas();
canvas.isDrawingMode = false;
}
}
export default FreeDrawing;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Add icon module
*/
import fabric from 'fabric';
import snippet from 'tui-code-snippet';
import { Promise } from '../util';
import Component from '../interface/component';
import { eventNames as events, rejectMessages, componentNames, fObjectOptions } from '../consts';
const pathMap = {
arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z',
cancel:
'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' +
'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z',
};
/**
* Icon
* @class Icon
* @param {Graphics} graphics - Graphics instance
* @extends {Component}
* @ignore
*/
class Icon extends Component {
constructor(graphics) {
super(componentNames.ICON, graphics);
/**
* Default icon color
* @type {string}
*/
this._oColor = '#000000';
/**
* Path value of each icon type
* @type {Object}
*/
this._pathMap = pathMap;
/**
* Type of the drawing icon
* @type {string}
* @private
*/
this._type = null;
/**
* Color of the drawing icon
* @type {string}
* @private
*/
this._iconColor = null;
/**
* Event handler list
* @type {Object}
* @private
*/
this._handlers = {
mousedown: this._onFabricMouseDown.bind(this),
mousemove: this._onFabricMouseMove.bind(this),
mouseup: this._onFabricMouseUp.bind(this),
};
}
/**
* Set states of the current drawing shape
* @ignore
* @param {string} type - Icon type ('arrow', 'cancel', custom icon name)
* @param {string} iconColor - Icon foreground color
*/
setStates(type, iconColor) {
this._type = type;
this._iconColor = iconColor;
}
/**
* Start to draw the icon on canvas
* @ignore
*/
start() {
const canvas = this.getCanvas();
canvas.selection = false;
canvas.on('mouse:down', this._handlers.mousedown);
}
/**
* End to draw the icon on canvas
* @ignore
*/
end() {
const canvas = this.getCanvas();
canvas.selection = true;
canvas.off({
'mouse:down': this._handlers.mousedown,
});
}
/**
* Add icon
* @param {string} type - Icon type
* @param {Object} options - Icon options
* @param {string} [options.fill] - Icon foreground color
* @param {string} [options.left] - Icon x position
* @param {string} [options.top] - Icon y position
* @returns {Promise}
*/
add(type, options) {
return new Promise((resolve, reject) => {
const canvas = this.getCanvas();
const path = this._pathMap[type];
const selectionStyle = fObjectOptions.SELECTION_STYLE;
const icon = path ? this._createIcon(path) : null;
this._icon = icon;
if (!icon) {
reject(rejectMessages.invalidParameters);
}
icon.set(
snippet.extend(
{
type: 'icon',
fill: this._oColor,
},
selectionStyle,
options,
this.graphics.controlStyle
)
);
canvas.add(icon).setActiveObject(icon);
resolve(this.graphics.createObjectProperties(icon));
});
}
/**
* Register icon paths
* @param {{key: string, value: string}} pathInfos - Path infos
*/
registerPaths(pathInfos) {
snippet.forEach(
pathInfos,
(path, type) => {
this._pathMap[type] = path;
},
this
);
}
/**
* Set icon object color
* @param {string} color - Color to set
* @param {fabric.Path}[obj] - Current activated path object
*/
setColor(color, obj) {
this._oColor = color;
if (obj && obj.get('type') === 'icon') {
obj.set({ fill: this._oColor });
this.getCanvas().renderAll();
}
}
/**
* Get icon color
* @param {fabric.Path}[obj] - Current activated path object
* @returns {string} color
*/
getColor(obj) {
return obj.fill;
}
/**
* Create icon object
* @param {string} path - Path value to create icon
* @returns {fabric.Path} Path object
*/
_createIcon(path) {
return new fabric.Path(path);
}
/**
* MouseDown event handler on canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
* @private
*/
_onFabricMouseDown(fEvent) {
const canvas = this.getCanvas();
this._startPoint = canvas.getPointer(fEvent.e);
const { x: left, y: top } = this._startPoint;
this.add(this._type, {
left,
top,
fill: this._iconColor,
}).then(() => {
this.fire(events.ADD_OBJECT, this.graphics.createObjectProperties(this._icon));
canvas.on('mouse:move', this._handlers.mousemove);
canvas.on('mouse:up', this._handlers.mouseup);
});
}
/**
* MouseMove event handler on canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
* @private
*/
_onFabricMouseMove(fEvent) {
const canvas = this.getCanvas();
if (!this._icon) {
return;
}
const moveOriginPointer = canvas.getPointer(fEvent.e);
const scaleX = (moveOriginPointer.x - this._startPoint.x) / this._icon.width;
const scaleY = (moveOriginPointer.y - this._startPoint.y) / this._icon.height;
this._icon.set({
scaleX: Math.abs(scaleX * 2),
scaleY: Math.abs(scaleY * 2),
});
this._icon.setCoords();
canvas.renderAll();
}
/**
* MouseUp event handler on canvas
* @private
*/
_onFabricMouseUp() {
const canvas = this.getCanvas();
this.fire(events.OBJECT_ADDED, this.graphics.createObjectProperties(this._icon));
this._icon = null;
canvas.off('mouse:down', this._handlers.mousedown);
canvas.off('mouse:move', this._handlers.mousemove);
canvas.off('mouse:up', this._handlers.mouseup);
}
}
export default Icon;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Image loader
*/
import Component from '../interface/component';
import { componentNames, rejectMessages } from '../consts';
import { Promise } from '../util';
const imageOption = {
padding: 0,
crossOrigin: 'Anonymous',
};
/**
* ImageLoader components
* @extends {Component}
* @class ImageLoader
* @param {Graphics} graphics - Graphics instance
* @ignore
*/
class ImageLoader extends Component {
constructor(graphics) {
super(componentNames.IMAGE_LOADER, graphics);
}
/**
* Load image from url
* @param {?string} imageName - File name
* @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image
* @returns {Promise}
*/
load(imageName, img) {
let promise;
if (!imageName && !img) {
// Back to the initial state, not error.
const canvas = this.getCanvas();
canvas.backgroundImage = null;
canvas.renderAll();
promise = new Promise((resolve) => {
this.setCanvasImage('', null);
resolve();
});
} else {
promise = this._setBackgroundImage(img).then((oImage) => {
this.setCanvasImage(imageName, oImage);
this.adjustCanvasDimension();
return oImage;
});
}
return promise;
}
/**
* Set background image
* @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to
* @returns {Promise}
* @private
*/
_setBackgroundImage(img) {
if (!img) {
return Promise.reject(rejectMessages.loadImage);
}
return new Promise((resolve, reject) => {
const canvas = this.getCanvas();
canvas.setBackgroundImage(
img,
() => {
const oImage = canvas.backgroundImage;
if (oImage && oImage.getElement()) {
resolve(oImage);
} else {
reject(rejectMessages.loadingImageFailed);
}
},
imageOption
);
});
}
}
export default ImageLoader;
/**
* @author NHN. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Free drawing module, Set brush
*/
import fabric from 'fabric';
import snippet from 'tui-code-snippet';
import Component from '../interface/component';
import ArrowLine from '../extension/arrowLine';
import { eventNames, componentNames, fObjectOptions } from '../consts';
/**
* Line
* @class Line
* @param {Graphics} graphics - Graphics instance
* @extends {Component}
* @ignore
*/
class Line extends Component {
constructor(graphics) {
super(componentNames.LINE, graphics);
/**
* Brush width
* @type {number}
* @private
*/
this._width = 12;
/**
* fabric.Color instance for brush color
* @type {fabric.Color}
* @private
*/
this._oColor = new fabric.Color('rgba(0, 0, 0, 0.5)');
/**
* Listeners
* @type {object.<string, function>}
* @private
*/
this._listeners = {
mousedown: this._onFabricMouseDown.bind(this),
mousemove: this._onFabricMouseMove.bind(this),
mouseup: this._onFabricMouseUp.bind(this),
};
}
/**
* Start drawing line mode
* @param {{width: ?number, color: ?string}} [setting] - Brush width & color
*/
setHeadOption(setting) {
const {
arrowType = {
head: null,
tail: null,
},
} = setting;
this._arrowType = arrowType;
}
/**
* Start drawing line mode
* @param {{width: ?number, color: ?string}} [setting] - Brush width & color
*/
start(setting = {}) {
const canvas = this.getCanvas();
canvas.defaultCursor = 'crosshair';
canvas.selection = false;
this.setHeadOption(setting);
this.setBrush(setting);
canvas.forEachObject((obj) => {
obj.set({
evented: false,
});
});
canvas.on({
'mouse:down': this._listeners.mousedown,
});
}
/**
* Set brush
* @param {{width: ?number, color: ?string}} [setting] - Brush width & color
*/
setBrush(setting) {
const brush = this.getCanvas().freeDrawingBrush;
setting = setting || {};
this._width = setting.width || this._width;
if (setting.color) {
this._oColor = new fabric.Color(setting.color);
}
brush.width = this._width;
brush.color = this._oColor.toRgba();
}
/**
* End drawing line mode
*/
end() {
const canvas = this.getCanvas();
canvas.defaultCursor = 'default';
canvas.selection = true;
canvas.forEachObject((obj) => {
obj.set({
evented: true,
});
});
canvas.off('mouse:down', this._listeners.mousedown);
}
/**
* Mousedown event handler in fabric canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
* @private
*/
_onFabricMouseDown(fEvent) {
const canvas = this.getCanvas();
const { x, y } = canvas.getPointer(fEvent.e);
const points = [x, y, x, y];
this._line = new ArrowLine(points, {
stroke: this._oColor.toRgba(),
strokeWidth: this._width,
arrowType: this._arrowType,
evented: false,
});
this._line.set(fObjectOptions.SELECTION_STYLE);
canvas.add(this._line);
canvas.on({
'mouse:move': this._listeners.mousemove,
'mouse:up': this._listeners.mouseup,
});
this.fire(eventNames.ADD_OBJECT, this._createLineEventObjectProperties());
}
/**
* Mousemove event handler in fabric canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
* @private
*/
_onFabricMouseMove(fEvent) {
const canvas = this.getCanvas();
const pointer = canvas.getPointer(fEvent.e);
this._line.set({
x2: pointer.x,
y2: pointer.y,
});
this._line.setCoords();
canvas.renderAll();
}
/**
* Mouseup event handler in fabric canvas
* @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object
* @private
*/
_onFabricMouseUp() {
const canvas = this.getCanvas();
this.fire(eventNames.OBJECT_ADDED, this._createLineEventObjectProperties());
this._line = null;
canvas.off({
'mouse:move': this._listeners.mousemove,
'mouse:up': this._listeners.mouseup,
});
}
/**
* create line event object properties
* @returns {Object} properties line object
* @private
*/
_createLineEventObjectProperties() {
const params = this.graphics.createObjectProperties(this._line);
const { x1, x2, y1, y2 } = this._line;
return snippet.extend({}, params, {
startPosition: {
x: x1,
y: y1,
},
endPosition: {
x: x2,
y: y2,
},
});
}
}
export default Line;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Image rotation module
*/
import fabric from 'fabric';
import { Promise } from '../util';
import Component from '../interface/component';
import { componentNames } from '../consts';
/**
* Image Rotation component
* @class Rotation
* @extends {Component}
* @param {Graphics} graphics - Graphics instance
* @ignore
*/
class Rotation extends Component {
constructor(graphics) {
super(componentNames.ROTATION, graphics);
}
/**
* Get current angle
* @returns {Number}
*/
getCurrentAngle() {
return this.getCanvasImage().angle;
}
/**
* Set angle of the image
*
* Do not call "this.setImageProperties" for setting angle directly.
* Before setting angle, The originX,Y of image should be set to center.
* See "http://fabricjs.com/docs/fabric.Object.html#setAngle"
*
* @param {number} angle - Angle value
* @returns {Promise}
*/
setAngle(angle) {
const oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees)
angle %= 360;
const canvasImage = this.getCanvasImage();
const oldImageCenter = canvasImage.getCenterPoint();
canvasImage.set({ angle }).setCoords();
this.adjustCanvasDimension();
const newImageCenter = canvasImage.getCenterPoint();
this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle);
return Promise.resolve(angle);
}
/**
* Rotate for each object
* @param {fabric.Point} oldImageCenter - Image center point before rotation
* @param {fabric.Point} newImageCenter - Image center point after rotation
* @param {number} angleDiff - Image angle difference after rotation
* @private
*/
_rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) {
const canvas = this.getCanvas();
const centerDiff = {
x: oldImageCenter.x - newImageCenter.x,
y: oldImageCenter.y - newImageCenter.y,
};
canvas.forEachObject((obj) => {
const objCenter = obj.getCenterPoint();
const radian = fabric.util.degreesToRadians(angleDiff);
const newObjCenter = fabric.util.rotatePoint(objCenter, oldImageCenter, radian);
obj.set({
left: newObjCenter.x - centerDiff.x,
top: newObjCenter.y - centerDiff.y,
angle: (obj.angle + angleDiff) % 360,
});
obj.setCoords();
});
canvas.renderAll();
}
/**
* Rotate the image
* @param {number} additionalAngle - Additional angle
* @returns {Promise}
*/
rotate(additionalAngle) {
const current = this.getCurrentAngle();
return this.setAngle(current + additionalAngle);
}
}
export default Rotation;
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Constants
*/
import { keyMirror } from './util';
/**
* Editor help features
* @type {Array.<string>}
*/
export const HELP_MENUS = ['undo', 'redo', 'reset', 'delete', 'deleteAll'];
/**
* Filter name value map
* @type {Object.<string, string>}
*/
export const FILTER_NAME_VALUE_MAP = {
blur: 'blur',
blocksize: 'pixelate',
};
/**
* Fill type for shape
* @type {Object.<string, string>}
*/
export const SHAPE_FILL_TYPE = {
FILTER: 'filter',
COLOR: 'color',
};
/**
* Shape type list
* @type {Array.<string>}
*/
export const SHAPE_TYPE = ['rect', 'circle', 'triangle'];
/**
* Component names
* @type {Object.<string, string>}
*/
export const componentNames = keyMirror(
'IMAGE_LOADER',
'CROPPER',
'FLIP',
'ROTATION',
'FREE_DRAWING',
'LINE',
'TEXT',
'ICON',
'FILTER',
'SHAPE'
);
/**
* Shape default option
* @type {Object}
*/
export const SHAPE_DEFAULT_OPTIONS = {
lockSkewingX: true,
lockSkewingY: true,
bringForward: true,
isRegular: false,
};
/**
* Cropzone default option
* @type {Object}
*/
export const CROPZONE_DEFAULT_OPTIONS = {
hasRotatingPoint: false,
hasBorders: false,
lockScalingFlip: true,
lockRotation: true,
lockSkewingX: true,
lockSkewingY: true,
};
/**
* Command names
* @type {Object.<string, string>}
*/
export const commandNames = {
CLEAR_OBJECTS: 'clearObjects',
LOAD_IMAGE: 'loadImage',
FLIP_IMAGE: 'flip',
ROTATE_IMAGE: 'rotate',
ADD_OBJECT: 'addObject',
REMOVE_OBJECT: 'removeObject',
APPLY_FILTER: 'applyFilter',
REMOVE_FILTER: 'removeFilter',
ADD_ICON: 'addIcon',
CHANGE_ICON_COLOR: 'changeIconColor',
ADD_SHAPE: 'addShape',
CHANGE_SHAPE: 'changeShape',
ADD_TEXT: 'addText',
CHANGE_TEXT: 'changeText',
CHANGE_TEXT_STYLE: 'changeTextStyle',
ADD_IMAGE_OBJECT: 'addImageObject',
RESIZE_CANVAS_DIMENSION: 'resizeCanvasDimension',
SET_OBJECT_PROPERTIES: 'setObjectProperties',
SET_OBJECT_POSITION: 'setObjectPosition',
CHANGE_SELECTION: 'changeSelection',
};
/**
* Event names
* @type {Object.<string, string>}
*/
export const eventNames = {
OBJECT_ACTIVATED: 'objectActivated',
OBJECT_MOVED: 'objectMoved',
OBJECT_SCALED: 'objectScaled',
OBJECT_CREATED: 'objectCreated',
OBJECT_ROTATED: 'objectRotated',
OBJECT_ADDED: 'objectAdded',
OBJECT_MODIFIED: 'objectModified',
TEXT_EDITING: 'textEditing',
TEXT_CHANGED: 'textChanged',
ICON_CREATE_RESIZE: 'iconCreateResize',
ICON_CREATE_END: 'iconCreateEnd',
ADD_TEXT: 'addText',
ADD_OBJECT: 'addObject',
ADD_OBJECT_AFTER: 'addObjectAfter',
MOUSE_DOWN: 'mousedown',
MOUSE_UP: 'mouseup',
MOUSE_MOVE: 'mousemove',
// UNDO/REDO Events
REDO_STACK_CHANGED: 'redoStackChanged',
UNDO_STACK_CHANGED: 'undoStackChanged',
SELECTION_CLEARED: 'selectionCleared',
SELECTION_CREATED: 'selectionCreated',
};
/**
* Editor states
* @type {Object.<string, string>}
*/
export const drawingModes = keyMirror(
'NORMAL',
'CROPPER',
'FREE_DRAWING',
'LINE_DRAWING',
'TEXT',
'SHAPE',
'ICON'
);
/**
* Shortcut key values
* @type {Object.<string, number>}
*/
export const keyCodes = {
Z: 90,
Y: 89,
C: 67,
V: 86,
SHIFT: 16,
BACKSPACE: 8,
DEL: 46,
ARROW_DOWN: 40,
ARROW_UP: 38,
};
/**
* Fabric object options
* @type {Object.<string, Object>}
*/
export const fObjectOptions = {
SELECTION_STYLE: {
borderColor: 'red',
cornerColor: 'green',
cornerSize: 10,
originX: 'center',
originY: 'center',
transparentCorners: false,
},
};
/**
* Promise reject messages
* @type {Object.<string, string>}
*/
export const rejectMessages = {
addedObject: 'The object is already added.',
flip: 'The flipX and flipY setting values are not changed.',
invalidDrawingMode: 'This operation is not supported in the drawing mode.',
invalidParameters: 'Invalid parameters.',
isLock: 'The executing command state is locked.',
loadImage: 'The background image is empty.',
loadingImageFailed: 'Invalid image loaded.',
noActiveObject: 'There is no active object.',
noObject: 'The object is not in canvas.',
redo: 'The promise of redo command is reject.',
rotation: 'The current angle is same the old angle.',
undo: 'The promise of undo command is reject.',
unsupportedOperation: 'Unsupported operation.',
unsupportedType: 'Unsupported object type.',
};
/**
* Default icon menu svg path
* @type {Object.<string, string>}
*/
export const defaultIconPath = {
'icon-arrow': 'M40 12V0l24 24-24 24V36H0V12h40z',
'icon-arrow-2': 'M49,32 H3 V22 h46 l-18,-18 h12 l23,23 L43,50 h-12 l18,-18 z ',
'icon-arrow-3':
'M43.349998,27 L17.354,53 H1.949999 l25.996,-26 L1.949999,1 h15.404 L43.349998,27 z ',
'icon-star':
'M35,54.557999 l-19.912001,10.468 l3.804,-22.172001 l-16.108,-15.7 l22.26,-3.236 L35,3.746 l9.956,20.172001 l22.26,3.236 l-16.108,15.7 l3.804,22.172001 z ',
'icon-star-2':
'M17,31.212 l-7.194,4.08 l-4.728,-6.83 l-8.234,0.524 l-1.328,-8.226 l-7.644,-3.14 l2.338,-7.992 l-5.54,-6.18 l5.54,-6.176 l-2.338,-7.994 l7.644,-3.138 l1.328,-8.226 l8.234,0.522 l4.728,-6.83 L17,-24.312 l7.194,-4.08 l4.728,6.83 l8.234,-0.522 l1.328,8.226 l7.644,3.14 l-2.338,7.992 l5.54,6.178 l-5.54,6.178 l2.338,7.992 l-7.644,3.14 l-1.328,8.226 l-8.234,-0.524 l-4.728,6.83 z ',
'icon-polygon': 'M3,31 L19,3 h32 l16,28 l-16,28 H19 z ',
'icon-location':
'M24 62C8 45.503 0 32.837 0 24 0 10.745 10.745 0 24 0s24 10.745 24 24c0 8.837-8 21.503-24 38zm0-28c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z',
'icon-heart':
'M49.994999,91.349998 l-6.96,-6.333 C18.324001,62.606995 2.01,47.829002 2.01,29.690998 C2.01,14.912998 13.619999,3.299999 28.401001,3.299999 c8.349,0 16.362,5.859 21.594,12 c5.229,-6.141 13.242001,-12 21.591,-12 c14.778,0 26.390999,11.61 26.390999,26.390999 c0,18.138 -16.314001,32.916 -41.025002,55.374001 l-6.96,6.285 z ',
'icon-bubble':
'M44 48L34 58V48H12C5.373 48 0 42.627 0 36V12C0 5.373 5.373 0 12 0h40c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12h-8z',
};
export const defaultRotateRangeValus = {
realTimeEvent: true,
min: -360,
max: 360,
value: 0,
};
export const defaultDrawRangeValus = {
min: 5,
max: 30,
value: 12,
};
export const defaultShapeStrokeValus = {
realTimeEvent: true,
min: 2,
max: 300,
value: 3,
};
export const defaultTextRangeValus = {
realTimeEvent: true,
min: 10,
max: 100,
value: 50,
};
export const defaultFilterRangeValus = {
tintOpacityRange: {
realTimeEvent: true,
min: 0,
max: 1,
value: 0.7,
useDecimal: true,
},
removewhiteDistanceRange: {
realTimeEvent: true,
min: 0,
max: 1,
value: 0.2,
useDecimal: true,
},
brightnessRange: {
realTimeEvent: true,
min: -1,
max: 1,
value: 0,
useDecimal: true,
},
noiseRange: {
realTimeEvent: true,
min: 0,
max: 1000,
value: 100,
},
pixelateRange: {
realTimeEvent: true,
min: 2,
max: 20,
value: 4,
},
colorfilterThresholeRange: {
realTimeEvent: true,
min: 0,
max: 1,
value: 0.2,
useDecimal: true,
},
blurFilterRange: {
value: 0.1,
},
};
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview CropperDrawingMode class
*/
import DrawingMode from '../interface/drawingMode';
import { drawingModes, componentNames as components } from '../consts';
/**
* CropperDrawingMode class
* @class
* @ignore
*/
class CropperDrawingMode extends DrawingMode {
constructor() {
super(drawingModes.CROPPER);
}
/**
* start this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
start(graphics) {
const cropper = graphics.getComponent(components.CROPPER);
cropper.start();
}
/**
* stop this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
end(graphics) {
const cropper = graphics.getComponent(components.CROPPER);
cropper.end();
}
}
export default CropperDrawingMode;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview FreeDrawingMode class
*/
import DrawingMode from '../interface/drawingMode';
import { drawingModes, componentNames as components } from '../consts';
/**
* FreeDrawingMode class
* @class
* @ignore
*/
class FreeDrawingMode extends DrawingMode {
constructor() {
super(drawingModes.FREE_DRAWING);
}
/**
* start this drawing mode
* @param {Graphics} graphics - Graphics instance
* @param {{width: ?number, color: ?string}} [options] - Brush width & color
* @override
*/
start(graphics, options) {
const freeDrawing = graphics.getComponent(components.FREE_DRAWING);
freeDrawing.start(options);
}
/**
* stop this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
end(graphics) {
const freeDrawing = graphics.getComponent(components.FREE_DRAWING);
freeDrawing.end();
}
}
export default FreeDrawingMode;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview IconDrawingMode class
*/
import DrawingMode from '../interface/drawingMode';
import { drawingModes, componentNames as components } from '../consts';
/**
* IconDrawingMode class
* @class
* @ignore
*/
class IconDrawingMode extends DrawingMode {
constructor() {
super(drawingModes.ICON);
}
/**
* start this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
start(graphics) {
const icon = graphics.getComponent(components.ICON);
icon.start();
}
/**
* stop this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
end(graphics) {
const icon = graphics.getComponent(components.ICON);
icon.end();
}
}
export default IconDrawingMode;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview LineDrawingMode class
*/
import DrawingMode from '../interface/drawingMode';
import { drawingModes, componentNames as components } from '../consts';
/**
* LineDrawingMode class
* @class
* @ignore
*/
class LineDrawingMode extends DrawingMode {
constructor() {
super(drawingModes.LINE_DRAWING);
}
/**
* start this drawing mode
* @param {Graphics} graphics - Graphics instance
* @param {{width: ?number, color: ?string}} [options] - Brush width & color
* @override
*/
start(graphics, options) {
const lineDrawing = graphics.getComponent(components.LINE);
lineDrawing.start(options);
}
/**
* stop this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
end(graphics) {
const lineDrawing = graphics.getComponent(components.LINE);
lineDrawing.end();
}
}
export default LineDrawingMode;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview ShapeDrawingMode class
*/
import DrawingMode from '../interface/drawingMode';
import { drawingModes, componentNames as components } from '../consts';
/**
* ShapeDrawingMode class
* @class
* @ignore
*/
class ShapeDrawingMode extends DrawingMode {
constructor() {
super(drawingModes.SHAPE);
}
/**
* start this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
start(graphics) {
const shape = graphics.getComponent(components.SHAPE);
shape.start();
}
/**
* stop this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
end(graphics) {
const shape = graphics.getComponent(components.SHAPE);
shape.end();
}
}
export default ShapeDrawingMode;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview TextDrawingMode class
*/
import DrawingMode from '../interface/drawingMode';
import { drawingModes, componentNames as components } from '../consts';
/**
* TextDrawingMode class
* @class
* @ignore
*/
class TextDrawingMode extends DrawingMode {
constructor() {
super(drawingModes.TEXT);
}
/**
* start this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
start(graphics) {
const text = graphics.getComponent(components.TEXT);
text.start();
}
/**
* stop this drawing mode
* @param {Graphics} graphics - Graphics instance
* @override
*/
end(graphics) {
const text = graphics.getComponent(components.TEXT);
text.end();
}
}
export default TextDrawingMode;
/**
* @author NHN. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Blur extending fabric.Image.filters.Convolute
*/
import fabric from 'fabric';
const ARROW_ANGLE = 30;
const CHEVRON_SIZE_RATIO = 2.7;
const TRIANGLE_SIZE_RATIO = 1.7;
const RADIAN_CONVERSION_VALUE = 180;
const ArrowLine = fabric.util.createClass(
fabric.Line,
/** @lends Convolute.prototype */ {
/**
* Line type
* @param {String} type
* @default
*/
type: 'line',
/**
* Constructor
* @param {Array} [points] Array of points
* @param {Object} [options] Options object
* @override
*/
initialize(points, options = {}) {
this.callSuper('initialize', points, options);
this.arrowType = options.arrowType;
},
/**
* Render ArrowLine
* @private
* @override
*/
_render(ctx) {
const { x1: fromX, y1: fromY, x2: toX, y2: toY } = this.calcLinePoints();
const linePosition = {
fromX,
fromY,
toX,
toY,
};
this.ctx = ctx;
ctx.lineWidth = this.strokeWidth;
this._renderBasicLinePath(linePosition);
this._drawDecoratorPath(linePosition);
this._renderStroke(ctx);
},
/**
* Render Basic line path
* @param {Object} linePosition - line position
* @param {number} option.fromX - line start position x
* @param {number} option.fromY - line start position y
* @param {number} option.toX - line end position x
* @param {number} option.toY - line end position y
* @private
*/
_renderBasicLinePath({ fromX, fromY, toX, toY }) {
this.ctx.beginPath();
this.ctx.moveTo(fromX, fromY);
this.ctx.lineTo(toX, toY);
},
/**
* Render Arrow Head
* @param {Object} linePosition - line position
* @param {number} option.fromX - line start position x
* @param {number} option.fromY - line start position y
* @param {number} option.toX - line end position x
* @param {number} option.toY - line end position y
* @private
*/
_drawDecoratorPath(linePosition) {
this._drawDecoratorPathType('head', linePosition);
this._drawDecoratorPathType('tail', linePosition);
},
/**
* Render Arrow Head
* @param {string} type - 'head' or 'tail'
* @param {Object} linePosition - line position
* @param {number} option.fromX - line start position x
* @param {number} option.fromY - line start position y
* @param {number} option.toX - line end position x
* @param {number} option.toY - line end position y
* @private
*/
_drawDecoratorPathType(type, linePosition) {
switch (this.arrowType[type]) {
case 'triangle':
this._drawTrianglePath(type, linePosition);
break;
case 'chevron':
this._drawChevronPath(type, linePosition);
break;
default:
break;
}
},
/**
* Render Triangle Head
* @param {string} type - 'head' or 'tail'
* @param {Object} linePosition - line position
* @param {number} option.fromX - line start position x
* @param {number} option.fromY - line start position y
* @param {number} option.toX - line end position x
* @param {number} option.toY - line end position y
* @private
*/
_drawTrianglePath(type, linePosition) {
const decorateSize = this.ctx.lineWidth * TRIANGLE_SIZE_RATIO;
this._drawChevronPath(type, linePosition, decorateSize);
this.ctx.closePath();
},
/**
* Render Chevron Head
* @param {string} type - 'head' or 'tail'
* @param {Object} linePosition - line position
* @param {number} option.fromX - line start position x
* @param {number} option.fromY - line start position y
* @param {number} option.toX - line end position x
* @param {number} option.toY - line end position y
* @param {number} decorateSize - decorate size
* @private
*/
_drawChevronPath(type, { fromX, fromY, toX, toY }, decorateSize) {
const { ctx } = this;
if (!decorateSize) {
decorateSize = this.ctx.lineWidth * CHEVRON_SIZE_RATIO;
}
const [standardX, standardY] = type === 'head' ? [fromX, fromY] : [toX, toY];
const [compareX, compareY] = type === 'head' ? [toX, toY] : [fromX, fromY];
const angle =
(Math.atan2(compareY - standardY, compareX - standardX) * RADIAN_CONVERSION_VALUE) /
Math.PI;
const rotatedPosition = (changeAngle) =>
this.getRotatePosition(decorateSize, changeAngle, {
x: standardX,
y: standardY,
});
ctx.moveTo(...rotatedPosition(angle + ARROW_ANGLE));
ctx.lineTo(standardX, standardY);
ctx.lineTo(...rotatedPosition(angle - ARROW_ANGLE));
},
/**
* return position from change angle.
* @param {number} distance - change distance
* @param {number} angle - change angle
* @param {Object} referencePosition - reference position
* @returns {Array}
* @private
*/
getRotatePosition(distance, angle, referencePosition) {
const radian = (angle * Math.PI) / RADIAN_CONVERSION_VALUE;
const { x, y } = referencePosition;
return [distance * Math.cos(radian) + x, distance * Math.sin(radian) + y];
},
}
);
export default ArrowLine;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Blur extending fabric.Image.filters.Convolute
*/
import fabric from 'fabric';
/**
* Blur object
* @class Blur
* @extends {fabric.Image.filters.Convolute}
* @ignore
*/
const Blur = fabric.util.createClass(
fabric.Image.filters.Convolute,
/** @lends Convolute.prototype */ {
/**
* Filter type
* @param {String} type
* @default
*/
type: 'Blur',
/**
* constructor
* @override
*/
initialize() {
this.matrix = [1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9];
},
}
);
export default Blur;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview ColorFilter extending fabric.Image.filters.BaseFilter
*/
import fabric from 'fabric';
/**
* ColorFilter object
* @class ColorFilter
* @extends {fabric.Image.filters.BaseFilter}
* @ignore
*/
const ColorFilter = fabric.util.createClass(
fabric.Image.filters.BaseFilter,
/** @lends BaseFilter.prototype */ {
/**
* Filter type
* @param {String} type
* @default
*/
type: 'ColorFilter',
/**
* Constructor
* @member fabric.Image.filters.ColorFilter.prototype
* @param {Object} [options] Options object
* @param {Number} [options.color='#FFFFFF'] Value of color (0...255)
* @param {Number} [options.threshold=45] Value of threshold (0...255)
* @override
*/
initialize(options) {
if (!options) {
options = {};
}
this.color = options.color || '#FFFFFF';
this.threshold = options.threshold || 45;
this.x = options.x || null;
this.y = options.y || null;
},
/**
* Applies filter to canvas element
* @param {Object} canvas Canvas object passed by fabric
*/
// eslint-disable-next-line complexity
applyTo(canvas) {
const { canvasEl } = canvas;
const context = canvasEl.getContext('2d');
const imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height);
const { data } = imageData;
const { threshold } = this;
let filterColor = fabric.Color.sourceFromHex(this.color);
let i, len;
if (this.x && this.y) {
filterColor = this._getColor(imageData, this.x, this.y);
}
for (i = 0, len = data.length; i < len; i += 4) {
if (
this._isOutsideThreshold(data[i], filterColor[0], threshold) ||
this._isOutsideThreshold(data[i + 1], filterColor[1], threshold) ||
this._isOutsideThreshold(data[i + 2], filterColor[2], threshold)
) {
continue;
}
data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0;
}
context.putImageData(imageData, 0, 0);
},
/**
* Check color if it is within threshold
* @param {Number} color1 source color
* @param {Number} color2 filtering color
* @param {Number} threshold threshold
* @returns {boolean} true if within threshold or false
*/
_isOutsideThreshold(color1, color2, threshold) {
const diff = color1 - color2;
return Math.abs(diff) > threshold;
},
/**
* Get color at (x, y)
* @param {Object} imageData of canvas
* @param {Number} x left position
* @param {Number} y top position
* @returns {Array} color array
*/
_getColor(imageData, x, y) {
const color = [0, 0, 0, 0];
const { data, width } = imageData;
const bytes = 4;
const position = (width * y + x) * bytes;
color[0] = data[position];
color[1] = data[position + 1];
color[2] = data[position + 2];
color[3] = data[position + 3];
return color;
},
}
);
export default ColorFilter;
This diff is collapsed. Click to expand it.
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Emboss extending fabric.Image.filters.Convolute
*/
import fabric from 'fabric';
/**
* Emboss object
* @class Emboss
* @extends {fabric.Image.filters.Convolute}
* @ignore
*/
const Emboss = fabric.util.createClass(
fabric.Image.filters.Convolute,
/** @lends Convolute.prototype */ {
/**
* Filter type
* @param {String} type
* @default
*/
type: 'Emboss',
/**
* constructor
* @override
*/
initialize() {
const matrix = [1, 1, 1, 1, 0.7, -1, -1, -1, -1];
this.matrix = matrix;
},
}
);
export default Emboss;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Mask extending fabric.Image.filters.Mask
*/
import fabric from 'fabric';
/**
* Mask object
* @class Mask
* @extends {fabric.Image.filters.BlendImage}
* @ignore
*/
const Mask = fabric.util.createClass(
fabric.Image.filters.BlendImage,
/** @lends Mask.prototype */ {
/**
* Apply filter to canvas element
* @param {Object} pipelineState - Canvas element to apply filter
* @override
*/
applyTo(pipelineState) {
if (!this.mask) {
return;
}
const canvas = pipelineState.canvasEl;
const { width, height } = canvas;
const maskCanvasEl = this._createCanvasOfMask(width, height);
const ctx = canvas.getContext('2d');
const maskCtx = maskCanvasEl.getContext('2d');
const imageData = ctx.getImageData(0, 0, width, height);
this._drawMask(maskCtx, canvas, ctx);
this._mapData(maskCtx, imageData, width, height);
pipelineState.imageData = imageData;
},
/**
* Create canvas of mask image
* @param {number} width - Width of main canvas
* @param {number} height - Height of main canvas
* @returns {HTMLElement} Canvas element
* @private
*/
_createCanvasOfMask(width, height) {
const maskCanvasEl = fabric.util.createCanvasElement();
maskCanvasEl.width = width;
maskCanvasEl.height = height;
return maskCanvasEl;
},
/**
* Draw mask image on canvas element
* @param {Object} maskCtx - Context of mask canvas
* @private
*/
_drawMask(maskCtx) {
const { mask } = this;
const maskImg = mask.getElement();
const { angle, left, scaleX, scaleY, top } = mask;
maskCtx.save();
maskCtx.translate(left, top);
maskCtx.rotate((angle * Math.PI) / 180);
maskCtx.scale(scaleX, scaleY);
maskCtx.drawImage(maskImg, -maskImg.width / 2, -maskImg.height / 2);
maskCtx.restore();
},
/**
* Map mask image data to source image data
* @param {Object} maskCtx - Context of mask canvas
* @param {Object} imageData - Data of source image
* @param {number} width - Width of main canvas
* @param {number} height - Height of main canvas
* @private
*/
_mapData(maskCtx, imageData, width, height) {
const { data, height: imgHeight, width: imgWidth } = imageData;
const sourceData = data;
const len = imgWidth * imgHeight * 4;
const maskData = maskCtx.getImageData(0, 0, width, height).data;
for (let i = 0; i < len; i += 4) {
sourceData[i + 3] = maskData[i]; // adjust value of alpha data
}
},
}
);
export default Mask;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Sharpen extending fabric.Image.filters.Convolute
*/
import fabric from 'fabric';
/**
* Sharpen object
* @class Sharpen
* @extends {fabric.Image.filters.Convolute}
* @ignore
*/
const Sharpen = fabric.util.createClass(
fabric.Image.filters.Convolute,
/** @lends Convolute.prototype */ {
/**
* Filter type
* @param {String} type
* @default
*/
type: 'Sharpen',
/**
* constructor
* @override
*/
initialize() {
const matrix = [0, -1, 0, -1, 5, -1, 0, -1, 0];
this.matrix = matrix;
},
}
);
export default Sharpen;
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Command factory
*/
import Command from '../interface/command';
const commands = {};
/**
* Create a command
* @param {string} name - Command name
* @param {...*} args - Arguments for creating command
* @returns {Command}
* @ignore
*/
function create(name, ...args) {
const actions = commands[name];
if (actions) {
return new Command(actions, args);
}
return null;
}
/**
* Register a command with name as a key
* @param {Object} command - {name:{string}, execute: {function}, undo: {function}}
* @param {string} command.name - command name
* @param {function} command.execute - executable function
* @param {function} command.undo - undo function
* @ignore
*/
function register(command) {
commands[command.name] = command;
}
export default {
create,
register,
};
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Error-message factory
*/
import snippet from 'tui-code-snippet';
import { keyMirror } from '../util';
const types = keyMirror('UN_IMPLEMENTATION', 'NO_COMPONENT_NAME');
const messages = {
UN_IMPLEMENTATION: 'Should implement a method: ',
NO_COMPONENT_NAME: 'Should set a component name',
};
const map = {
UN_IMPLEMENTATION(methodName) {
return messages.UN_IMPLEMENTATION + methodName;
},
NO_COMPONENT_NAME() {
return messages.NO_COMPONENT_NAME;
},
};
export default {
types: snippet.extend({}, types),
create(type, ...args) {
type = type.toLowerCase();
const func = map[type];
return func(...args);
},
};
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
/**
* @author NHN. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Selection modification helper
*/
import { extend } from 'tui-code-snippet/src/js/object';
/**
* Cached selection's info
* @type {Array}
* @private
*/
let cachedUndoDataForChangeDimension = null;
/**
* Set cached undo data
* @param {Array} undoData - selection object
* @private
*/
export function setCachedUndoDataForDimension(undoData) {
cachedUndoDataForChangeDimension = undoData;
}
/**
* Get cached undo data
* @returns {Object} cached undo data
* @private
*/
export function getCachedUndoDataForDimension() {
return cachedUndoDataForChangeDimension;
}
/**
* Make undo data
* @param {fabric.Object} obj - selection object
* @param {Function} undoDatumMaker - make undo datum
* @returns {Array} undoData
* @private
*/
export function makeSelectionUndoData(obj, undoDatumMaker) {
let undoData;
if (obj.type === 'activeSelection') {
undoData = obj.getObjects().map((item) => {
const { angle, left, top, scaleX, scaleY, width, height } = item;
obj.realizeTransform(item);
const result = undoDatumMaker(item);
item.set({
angle,
left,
top,
width,
height,
scaleX,
scaleY,
});
return result;
});
} else {
undoData = [undoDatumMaker(obj)];
}
return undoData;
}
/**
* Make undo datum
* @param {number} id - object id
* @param {fabric.Object} obj - selection object
* @param {boolean} isSelection - whether or not object is selection
* @returns {Object} undo datum
* @private
*/
export function makeSelectionUndoDatum(id, obj, isSelection) {
return isSelection
? {
id,
width: obj.width,
height: obj.height,
top: obj.top,
left: obj.left,
angle: obj.angle,
scaleX: obj.scaleX,
scaleY: obj.scaleY,
}
: extend({ id }, obj);
}
This diff is collapsed. Click to expand it.
/**
* @author NHN Ent. FE Development Team <dl_javascript@nhn.com>
* @fileoverview Shape resize helper
*/
const DIVISOR = {
rect: 1,
circle: 2,
triangle: 1,
};
const DIMENSION_KEYS = {
rect: {
w: 'width',
h: 'height',
},
circle: {
w: 'rx',
h: 'ry',
},
triangle: {
w: 'width',
h: 'height',
},
};
/**
* Set the start point value to the shape object
* @param {fabric.Object} shape - Shape object
* @ignore
*/
function setStartPoint(shape) {
const { originX, originY } = shape;
const originKey = originX.substring(0, 1) + originY.substring(0, 1);
shape.startPoint = shape.origins[originKey];
}
/**
* Get the positions of ratated origin by the pointer value
* @param {{x: number, y: number}} origin - Origin value
* @param {{x: number, y: number}} pointer - Pointer value
* @param {number} angle - Rotating angle
* @returns {Object} Postions of origin
* @ignore
*/
function getPositionsOfRotatedOrigin(origin, pointer, angle) {
const sx = origin.x;
const sy = origin.y;
const px = pointer.x;
const py = pointer.y;
const r = (angle * Math.PI) / 180;
const rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx;
const ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy;
return {
originX: sx > rx ? 'right' : 'left',
originY: sy > ry ? 'bottom' : 'top',
};
}
/**
* Whether the shape has the center origin or not
* @param {fabric.Object} shape - Shape object
* @returns {boolean} State
* @ignore
*/
function hasCenterOrigin(shape) {
return shape.originX === 'center' && shape.originY === 'center';
}
/**
* Adjust the origin of shape by the start point
* @param {{x: number, y: number}} pointer - Pointer value
* @param {fabric.Object} shape - Shape object
* @ignore
*/
function adjustOriginByStartPoint(pointer, shape) {
const centerPoint = shape.getPointByOrigin('center', 'center');
const angle = -shape.angle;
const originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle);
const { originX, originY } = originPositions;
const origin = shape.getPointByOrigin(originX, originY);
const left = shape.left - (centerPoint.x - origin.x);
const top = shape.top - (centerPoint.y - origin.y);
shape.set({
originX,
originY,
left,
top,
});
shape.setCoords();
}
/**
* Adjust the origin of shape by the moving pointer value
* @param {{x: number, y: number}} pointer - Pointer value
* @param {fabric.Object} shape - Shape object
* @ignore
*/
function adjustOriginByMovingPointer(pointer, shape) {
const origin = shape.startPoint;
const angle = -shape.angle;
const originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle);
const { originX, originY } = originPositions;
shape.setPositionByOrigin(origin, originX, originY);
shape.setCoords();
}
/**
* Adjust the dimension of shape on firing scaling event
* @param {fabric.Object} shape - Shape object
* @ignore
*/
function adjustDimensionOnScaling(shape) {
const { type, scaleX, scaleY } = shape;
const dimensionKeys = DIMENSION_KEYS[type];
let width = shape[dimensionKeys.w] * scaleX;
let height = shape[dimensionKeys.h] * scaleY;
if (shape.isRegular) {
const maxScale = Math.max(scaleX, scaleY);
width = shape[dimensionKeys.w] * maxScale;
height = shape[dimensionKeys.h] * maxScale;
}
const options = {
hasControls: false,
hasBorders: false,
scaleX: 1,
scaleY: 1,
};
options[dimensionKeys.w] = width;
options[dimensionKeys.h] = height;
shape.set(options);
}
/**
* Adjust the dimension of shape on firing mouse move event
* @param {{x: number, y: number}} pointer - Pointer value
* @param {fabric.Object} shape - Shape object
* @ignore
*/
function adjustDimensionOnMouseMove(pointer, shape) {
const { type, strokeWidth, startPoint: origin } = shape;
const divisor = DIVISOR[type];
const dimensionKeys = DIMENSION_KEYS[type];
const isTriangle = !!(shape.type === 'triangle');
const options = {};
let width = Math.abs(origin.x - pointer.x) / divisor;
let height = Math.abs(origin.y - pointer.y) / divisor;
if (width > strokeWidth) {
width -= strokeWidth / divisor;
}
if (height > strokeWidth) {
height -= strokeWidth / divisor;
}
if (shape.isRegular) {
width = height = Math.max(width, height);
if (isTriangle) {
height = (Math.sqrt(3) / 2) * width;
}
}
options[dimensionKeys.w] = width;
options[dimensionKeys.h] = height;
shape.set(options);
}
module.exports = {
/**
* Set each origin value to shape
* @param {fabric.Object} shape - Shape object
*/
setOrigins(shape) {
const leftTopPoint = shape.getPointByOrigin('left', 'top');
const rightTopPoint = shape.getPointByOrigin('right', 'top');
const rightBottomPoint = shape.getPointByOrigin('right', 'bottom');
const leftBottomPoint = shape.getPointByOrigin('left', 'bottom');
shape.origins = {
lt: leftTopPoint,
rt: rightTopPoint,
rb: rightBottomPoint,
lb: leftBottomPoint,
};
},
/**
* Resize the shape
* @param {fabric.Object} shape - Shape object
* @param {{x: number, y: number}} pointer - Mouse pointer values on canvas
* @param {boolean} isScaling - Whether the resizing action is scaling or not
*/
resize(shape, pointer, isScaling) {
if (hasCenterOrigin(shape)) {
adjustOriginByStartPoint(pointer, shape);
setStartPoint(shape);
}
if (isScaling) {
adjustDimensionOnScaling(shape, pointer);
} else {
adjustDimensionOnMouseMove(pointer, shape);
}
adjustOriginByMovingPointer(pointer, shape);
},
/**
* Adjust the origin position of shape to center
* @param {fabric.Object} shape - Shape object
*/
adjustOriginToCenter(shape) {
const centerPoint = shape.getPointByOrigin('center', 'center');
const { originX, originY } = shape;
const origin = shape.getPointByOrigin(originX, originY);
const left = shape.left + (centerPoint.x - origin.x);
const top = shape.top + (centerPoint.y - origin.y);
shape.set({
hasControls: true,
hasBorders: true,
originX: 'center',
originY: 'center',
left,
top,
});
shape.setCoords(); // For left, top properties
},
};
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.