Add fileName property to models and update related components for file handling

This commit is contained in:
liosha84
2025-08-09 22:22:02 +03:00
parent ca73cf31df
commit d45fd83593
64 changed files with 1644 additions and 433 deletions
@@ -0,0 +1,103 @@
<!--
<div class="ngx-pe-overlay-container">
<div class="ngx-pe-dialog-container" [style.max-width]="modalMaxWidth">
<div class="ngx-pe-dialog-header" *ngIf="!hideModalHeader">
{{modalTitle}}
</div>
<div class="ngx-pe-dialog-body">
<div class="ngx-pe-cropper-wrapper">
<div class="ngx-pe-loading" *ngIf="!imageLoaded" [class.ngx-pe-processing]="isProcessing">
<svg xmlns="http://www.w3.org/2000/svg"
style="margin: auto; display: block; shape-rendering: auto; animation-play-state: running; animation-delay: 0s;"
viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" fill="none" stroke="#f3f3f3" stroke-width="10" r="35"
stroke-dasharray="164.93361431346415 56.97787143782138"
style="animation-play-state: running; animation-delay: 0s;">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s"
values="0 50 50;360 50 50" keyTimes="0;1"
style="animation-play-state: running; animation-delay: 0s;"></animateTransform>
</circle>
</svg>
</div>
<img class="ngx-pe-img" #image loading="lazy" (load)="onLoad()" (error)="error()" [src]="imgUrl" alt="cropper-img" crossorigin="anonymous">
<div class="ngx-pe-tool-bar">
&lt;!&ndash;move&ndash;&gt;
<button (click)="move()" title="Move">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M24 44 15.85 35.85 18.05 33.65 22.5 38.1V28.65H25.5V38.1L29.95 33.65L32.15 35.85ZM11.9 31.9 4 24 11.95 16.05 14.15 18.25 9.9 22.5H19.35V25.5H9.9L14.1 29.7ZM36.1 31.9 33.9 29.7 38.1 25.5H28.7V22.5H38.1L33.9 18.3L36.1 16.1L44 24ZM22.5 19.3V9.9L18.3 14.1L16.1 11.9L24 4L31.9 11.9L29.7 14.1L25.5 9.9V19.3Z"/>
</svg>
</button>
&lt;!&ndash;crop&ndash;&gt;
<button (click)="crop()" title="Crop">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M34.75 31.75V13.25Q34.75 13.25 34.75 13.25Q34.75 13.25 34.75 13.25H16.25V10.25H34.75Q35.95 10.25 36.85 11.15Q37.75 12.05 37.75 13.25V31.75ZM36.25 46Q35.6 46 35.175 45.575Q34.75 45.15 34.75 44.5V37.75H13.25Q12.05 37.75 11.15 36.85Q10.25 35.95 10.25 34.75V13.25H3.5Q2.85 13.25 2.425 12.825Q2 12.4 2 11.75Q2 11.1 2.425 10.675Q2.85 10.25 3.5 10.25H10.25V3.5Q10.25 2.85 10.675 2.425Q11.1 2 11.75 2Q12.4 2 12.825 2.425Q13.25 2.85 13.25 3.5V34.75Q13.25 34.75 13.25 34.75Q13.25 34.75 13.25 34.75H44.5Q45.15 34.75 45.575 35.175Q46 35.6 46 36.25Q46 36.9 45.575 37.325Q45.15 37.75 44.5 37.75H37.75V44.5Q37.75 45.15 37.325 45.575Q36.9 46 36.25 46Z"/>
</svg>
</button>
&lt;!&ndash;zoom in&ndash;&gt;
<button (click)="zoomIn()" title="Zoom In">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M38.7 40.85 26.65 28.8Q25.15 30.1 23.15 30.825Q21.15 31.55 18.9 31.55Q13.5 31.55 9.75 27.8Q6 24.05 6 18.75Q6 13.45 9.75 9.7Q13.5 5.95 18.85 5.95Q24.15 5.95 27.875 9.7Q31.6 13.45 31.6 18.75Q31.6 20.9 30.9 22.9Q30.2 24.9 28.8 26.65L40.95 38.7Q41.4 39.1 41.375 39.75Q41.35 40.4 40.9 40.85Q40.45 41.3 39.8 41.3Q39.15 41.3 38.7 40.85ZM18.85 28.55Q22.9 28.55 25.75 25.675Q28.6 22.8 28.6 18.75Q28.6 14.7 25.75 11.825Q22.9 8.95 18.85 8.95Q14.75 8.95 11.875 11.825Q9 14.7 9 18.75Q9 22.8 11.875 25.675Q14.75 28.55 18.85 28.55ZM18.8 24.3Q18.15 24.3 17.725 23.875Q17.3 23.45 17.3 22.8V20.2H14.65Q14.05 20.2 13.625 19.75Q13.2 19.3 13.2 18.65Q13.2 18.05 13.625 17.625Q14.05 17.2 14.7 17.2H17.3V14.6Q17.3 14 17.75 13.575Q18.2 13.15 18.85 13.15Q19.45 13.15 19.875 13.575Q20.3 14 20.3 14.65V17.2H22.9Q23.5 17.2 23.925 17.65Q24.35 18.1 24.35 18.7Q24.35 19.35 23.925 19.775Q23.5 20.2 22.85 20.2H20.3V22.85Q20.3 23.45 19.85 23.875Q19.4 24.3 18.8 24.3Z"/>
</svg>
</button>
&lt;!&ndash;zoom out&ndash;&gt;
<button (click)="zoomOut()" title="Zoom Out">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M38.7 40.85 26.65 28.8Q25.15 30.1 23.15 30.825Q21.15 31.55 18.9 31.55Q13.5 31.55 9.75 27.8Q6 24.05 6 18.75Q6 13.45 9.75 9.7Q13.5 5.95 18.85 5.95Q24.15 5.95 27.875 9.7Q31.6 13.45 31.6 18.75Q31.6 20.9 30.9 22.9Q30.2 24.9 28.8 26.65L40.95 38.7Q41.4 39.1 41.375 39.75Q41.35 40.4 40.9 40.85Q40.45 41.3 39.8 41.3Q39.15 41.3 38.7 40.85ZM18.85 28.55Q22.9 28.55 25.75 25.675Q28.6 22.8 28.6 18.75Q28.6 14.7 25.75 11.825Q22.9 8.95 18.85 8.95Q14.75 8.95 11.875 11.825Q9 14.7 9 18.75Q9 22.8 11.875 25.675Q14.75 28.55 18.85 28.55ZM15.2 20.2Q14.6 20.2 14.175 19.75Q13.75 19.3 13.75 18.65Q13.75 18.05 14.175 17.625Q14.6 17.2 15.25 17.2H22.35Q22.95 17.2 23.375 17.65Q23.8 18.1 23.8 18.7Q23.8 19.35 23.375 19.775Q22.95 20.2 22.3 20.2Z"/>
</svg>
</button>
&lt;!&ndash;rotate left&ndash;&gt;
<button (click)="rotateLeft()" title="Rotate Left">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M7.9 23.6Q7.15 23.6 6.7 23.075Q6.25 22.55 6.4 21.85Q6.75 20.3 7.325 18.85Q7.9 17.4 8.7 16.05Q9.1 15.4 9.775 15.35Q10.45 15.3 11 15.85Q11.4 16.2 11.45 16.75Q11.5 17.3 11.25 17.75Q10.55 18.9 10.075 20.075Q9.6 21.25 9.35 22.4Q9.25 22.95 8.85 23.275Q8.45 23.6 7.9 23.6ZM20.05 43.7Q18.6 43.35 17.15 42.775Q15.7 42.2 14.4 41.4Q13.75 41 13.7 40.3Q13.65 39.6 14.2 39Q14.55 38.65 15.05 38.6Q15.55 38.55 15.95 38.8Q17.1 39.5 18.225 39.975Q19.35 40.45 20.55 40.75Q21.05 40.85 21.4 41.275Q21.75 41.7 21.75 42.25Q21.75 43 21.25 43.425Q20.75 43.85 20.05 43.7ZM8.7 35.7Q7.9 34.4 7.325 32.925Q6.75 31.45 6.4 29.85Q6.25 29.15 6.675 28.625Q7.1 28.1 7.9 28.1Q8.4 28.1 8.825 28.425Q9.25 28.75 9.35 29.3Q9.6 30.55 10.05 31.725Q10.5 32.9 11.2 33.95Q11.5 34.4 11.45 34.95Q11.4 35.5 11 35.9Q10.45 36.45 9.775 36.4Q9.1 36.35 8.7 35.7ZM27.95 43.65Q27.25 43.85 26.75 43.425Q26.25 43 26.25 42.25Q26.25 41.75 26.6 41.325Q26.95 40.9 27.45 40.75Q32.5 39.4 35.75 35.375Q39 31.35 39 25.85Q39 19.5 34.675 15.175Q30.35 10.85 24 10.85H23L25.85 13.7Q26.3 14.15 26.3 14.8Q26.3 15.45 25.85 15.9Q25.4 16.35 24.75 16.35Q24.1 16.35 23.65 15.9L18.15 10.4Q17.9 10.15 17.8 9.9Q17.7 9.65 17.7 9.35Q17.7 9.05 17.8 8.8Q17.9 8.55 18.15 8.3L23.65 2.8Q24.1 2.35 24.75 2.35Q25.4 2.35 25.85 2.8Q26.3 3.25 26.3 3.9Q26.3 4.55 25.85 5L23 7.85H24Q31.55 7.85 36.775 13.075Q42 18.3 42 25.85Q42 32.4 38.075 37.275Q34.15 42.15 27.95 43.65Z"/>
</svg>
</button>
&lt;!&ndash;rotate right&ndash;&gt;
<button (click)="rotateRight()" title="Rotate Right">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M40.2 23.6Q39.7 23.6 39.275 23.25Q38.85 22.9 38.75 22.4Q38.5 21.2 38.025 20.05Q37.55 18.9 36.85 17.75Q36.6 17.3 36.65 16.75Q36.7 16.2 37.1 15.8Q37.65 15.25 38.35 15.325Q39.05 15.4 39.45 16.05Q40.25 17.35 40.8 18.775Q41.35 20.2 41.7 21.8Q41.85 22.5 41.4 23.05Q40.95 23.6 40.2 23.6ZM26.3 42.2Q26.3 41.7 26.65 41.275Q27 40.85 27.5 40.75Q28.65 40.5 29.8 40.025Q30.95 39.55 32.1 38.85Q32.55 38.6 33.075 38.625Q33.6 38.65 34 39.05Q34.55 39.6 34.475 40.3Q34.4 41 33.75 41.4Q32.4 42.25 30.975 42.8Q29.55 43.35 28.1 43.7Q27.4 43.85 26.85 43.4Q26.3 42.95 26.3 42.2ZM37.1 35.9Q36.75 35.55 36.7 34.975Q36.65 34.4 36.9 33.95Q37.6 32.8 38.05 31.65Q38.5 30.5 38.75 29.3Q38.85 28.8 39.25 28.45Q39.65 28.1 40.2 28.1Q40.95 28.1 41.375 28.625Q41.8 29.15 41.65 29.85Q41.25 31.45 40.7 32.9Q40.15 34.35 39.4 35.65Q39 36.3 38.325 36.375Q37.65 36.45 37.1 35.9ZM20.05 43.7Q13.9 42.3 10 37.35Q6.1 32.4 6.1 25.85Q6.1 18.3 11.325 13.075Q16.55 7.85 24.1 7.85H25.1L22.25 5Q21.8 4.55 21.8 3.9Q21.8 3.25 22.25 2.8Q22.7 2.35 23.35 2.35Q24 2.35 24.45 2.8L29.95 8.3Q30.2 8.55 30.3 8.8Q30.4 9.05 30.4 9.35Q30.4 9.65 30.3 9.9Q30.2 10.15 29.95 10.4L24.45 15.9Q24 16.35 23.35 16.35Q22.7 16.35 22.25 15.9Q21.8 15.45 21.8 14.8Q21.8 14.15 22.25 13.7L25.1 10.85H24.1Q17.75 10.85 13.425 15.175Q9.1 19.5 9.1 25.85Q9.1 31.35 12.35 35.375Q15.6 39.4 20.65 40.7Q21.15 40.8 21.5 41.225Q21.85 41.65 21.85 42.2Q21.85 42.95 21.3 43.4Q20.75 43.85 20.05 43.7Z"/>
</svg>
</button>
&lt;!&ndash;flip vertical&ndash;&gt;
<button (click)="flipH()" title="Flip Horizontal">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M9 42Q7.8 42 6.9 41.1Q6 40.2 6 39V9Q6 7.8 6.9 6.9Q7.8 6 9 6H18.1Q18.75 6 19.175 6.425Q19.6 6.85 19.6 7.5Q19.6 8.15 19.175 8.575Q18.75 9 18.1 9H9Q9 9 9 9Q9 9 9 9V39Q9 39 9 39Q9 39 9 39H18.1Q18.75 39 19.175 39.425Q19.6 39.85 19.6 40.5Q19.6 41.15 19.175 41.575Q18.75 42 18.1 42ZM24.1 46Q23.45 46 23.025 45.575Q22.6 45.15 22.6 44.5V3.5Q22.6 2.85 23.025 2.425Q23.45 2 24.1 2Q24.75 2 25.175 2.425Q25.6 2.85 25.6 3.5V44.5Q25.6 45.15 25.175 45.575Q24.75 46 24.1 46ZM39 9H38.3V6H39Q40.2 6 41.1 6.9Q42 7.8 42 9V9.7H39ZM39 26.75V21.25H42V26.75ZM39 42H38.3V39H39V38.3H42V39Q42 40.2 41.1 41.1Q40.2 42 39 42ZM39 18.25V12.7H42V18.25ZM39 35.3V29.75H42V35.3ZM28.6 42V39H35.3V42ZM28.6 9V6H35.3V9Z"/>
</svg>
</button>
&lt;!&ndash;flip horizontal&ndash;&gt;
<button (click)="flipV()" title="Flip Vertical">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" transform='rotate(90)'>
<path
d="M9 42Q7.8 42 6.9 41.1Q6 40.2 6 39V9Q6 7.8 6.9 6.9Q7.8 6 9 6H18.1Q18.75 6 19.175 6.425Q19.6 6.85 19.6 7.5Q19.6 8.15 19.175 8.575Q18.75 9 18.1 9H9Q9 9 9 9Q9 9 9 9V39Q9 39 9 39Q9 39 9 39H18.1Q18.75 39 19.175 39.425Q19.6 39.85 19.6 40.5Q19.6 41.15 19.175 41.575Q18.75 42 18.1 42ZM24.1 46Q23.45 46 23.025 45.575Q22.6 45.15 22.6 44.5V3.5Q22.6 2.85 23.025 2.425Q23.45 2 24.1 2Q24.75 2 25.175 2.425Q25.6 2.85 25.6 3.5V44.5Q25.6 45.15 25.175 45.575Q24.75 46 24.1 46ZM39 9H38.3V6H39Q40.2 6 41.1 6.9Q42 7.8 42 9V9.7H39ZM39 26.75V21.25H42V26.75ZM39 42H38.3V39H39V38.3H42V39Q42 40.2 41.1 41.1Q40.2 42 39 42ZM39 18.25V12.7H42V18.25ZM39 35.3V29.75H42V35.3ZM28.6 42V39H35.3V42ZM28.6 9V6H35.3V9Z"/>
</svg>
</button>
<button (click)="reset()" title="Reset">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
d="M9.8 31.45Q8.8 29.65 8.4 27.825Q8 26 8 24.1Q8 17.55 12.725 12.825Q17.45 8.1 24 8.1H26.15L22.15 4.1L24.1 2.15L31.55 9.6L24.1 17.05L22.1 15.05L26.05 11.1H24Q18.65 11.1 14.825 14.925Q11 18.75 11 24.1Q11 25.55 11.275 26.85Q11.55 28.15 11.95 29.3ZM23.8 46 16.35 38.55 23.8 31.1 25.75 33.05 21.75 37.05H24Q29.35 37.05 33.175 33.225Q37 29.4 37 24.05Q37 22.6 36.75 21.3Q36.5 20 36 18.85L38.15 16.7Q39.15 18.5 39.575 20.325Q40 22.15 40 24.05Q40 30.6 35.275 35.325Q30.55 40.05 24 40.05H21.75L25.75 44.05Z"/>
</svg>
</button>
</div>
</div>
</div>
<div class="ngx-pe-dialog-footer">
<button class="ngx-pe-btn ngx-pe-btn-outline-light" (click)="close()">{{closeBtnText}}</button>
<button class="ngx-pe-btn ngx-pe-btn-primary" (click)="export()">{{applyBtnText}}</button>
</div>
</div>
</div>
-->
@@ -0,0 +1,179 @@
@import "~node_modules/cropperjs/dist/cropper.css";
.ngx-pe-overlay-container {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.25);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
padding: 1rem;
box-sizing: border-box;
}
.ngx-pe-overlay-container * {
box-sizing: border-box;
}
.ngx-pe-dialog-container {
position: relative;
border-radius: 4px;
overflow: hidden;
outline: 0;
width: 100%;
height: auto;
max-height: 95vh;
box-shadow: 0 11px 15px -7px #0003, 0 24px 38px 3px #00000024, 0 9px 46px 8px #0000001f;
background: #333333;
color: white;
display: flex;
flex-direction: column;
}
.ngx-pe-dialog-header {
padding: 1rem 1rem 1rem 1rem;
font-size: 1rem;
font-weight: 400;
}
.ngx-pe-dialog-body {
height: 100%;
position: relative;
box-sizing: border-box;
}
.ngx-pe-dialog-footer {
padding: 1rem 1rem;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.ngx-pe-cropper-wrapper {
position: relative;
}
.ngx-pe-img {
display: block;
max-width: 100%;
height: auto;
max-height: 300px;
}
.ngx-pe-tool-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
background-color: #222;
box-sizing: border-box;
width: 100%;
}
.ngx-pe-tool-bar button {
background-color: transparent;
border-width: 0;
color: #fff;
cursor: pointer;
float: left;
font-size: 0.875rem;
height: 2.5rem;
display: flex;
justify-content: center;
align-items: center;
width: 2.5rem;
}
.ngx-pe-tool-bar button:hover {
background-color: #0074d9;
color: #fff;
}
.ngx-pe-tool-bar svg {
width: 20px;
height: 20px;
fill: white;
}
.ngx-pe-btn {
--bs-btn-padding-y: 0.25rem;
--bs-btn-padding-x: 1rem;
--bs-btn-font-size: 0.875rem;
--bs-btn-border-radius: 0.25rem;
--bs-btn-font-weight: 400;
--bs-btn-line-height: 1.5;
--bs-btn-color: #212529;
--bs-btn-bg: transparent;
--bs-btn-border-width: 1px;
--bs-btn-border-color: transparent;
--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
--bs-btn-disabled-opacity: 0.65;
--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb));
display: inline-block;
padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);
font-size: var(--bs-btn-font-size);
font-weight: var(--bs-btn-font-weight);
line-height: var(--bs-btn-line-height);
color: var(--bs-btn-color);
text-align: center;
text-decoration: none;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);
border-radius: var(--bs-btn-border-radius);
background-color: var(--bs-btn-bg);
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.ngx-pe-btn:hover {
color: var(--bs-btn-hover-color);
background-color: var(--bs-btn-hover-bg);
border-color: var(--bs-btn-hover-border-color);
}
.ngx-pe-btn-outline-light {
--bs-btn-color: #f8f9fa;
--bs-btn-border-color: #f8f9fa;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #f8f9fa;
--bs-btn-hover-border-color: #f8f9fa;
--bs-btn-focus-shadow-rgb: 248, 249, 250;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #f8f9fa;
--bs-btn-active-border-color: #f8f9fa;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #f8f9fa;
--bs-btn-disabled-bg: transparent;
--bs-gradient: none;
}
.ngx-pe-btn-primary {
--bs-btn-color: #fff;
--bs-btn-bg: #0d6efd;
--bs-btn-border-color: #0d6efd;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0b5ed7;
--bs-btn-hover-border-color: #0a58ca;
--bs-btn-focus-shadow-rgb: 49, 132, 253;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0a58ca;
--bs-btn-active-border-color: #0a53be;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #0d6efd;
--bs-btn-disabled-border-color: #0d6efd;
}
.ngx-pe-loading {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 2;
background-color: #222222;
display: flex;
justify-content: center;
align-items: center;
transition: all 10s ease;
}
.ngx-pe-processing {
background-color: rgba(34,34,34, 0.8);
}
.ngx-pe-loading svg {
width: 50px;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PhotoEditComponent } from './photo-edit.component';
describe('PhotoEditComponent', () => {
let component: PhotoEditComponent;
let fixture: ComponentFixture<PhotoEditComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PhotoEditComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PhotoEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,228 @@
/*
import {Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from '@angular/core';
import Cropper from "cropperjs";
import ViewMode = Cropper.ViewMode;
import {NgIf, NgOptimizedImage} from '@angular/common';
@Component({
selector: 'app-photo-edit.component',
imports: [
NgIf,
NgOptimizedImage
],
templateUrl: './photo-edit.component.html',
styleUrl: './photo-edit.component.scss'
})
export class PhotoEditComponent implements OnDestroy {
@ViewChild('image')
image!: ElementRef;
private cropper!: Cropper;
public imgUrl!: string;
private FILES_REGEX = /\.(gif|jpe?g|tiff|png|webp|bmp)$/i;
@Input() modalTitle:String = 'Photo Editor';
@Input() hideModalHeader = false;
@Input() aspectRatio = NaN;
@Input() autoCropArea = 0.8;
@Input() autoCrop = true;
@Input() mask = true;
@Input() guides = true;
@Input() centerIndicator = true;
@Input() viewMode:ViewMode = 0;
@Input() modalMaxWidth = '500px';
@Input() modalCentered = false;
@Input() scalable = true;
@Input() zoomable = true;
@Input() cropBoxMovable = true;
@Input() cropBoxResizable = true;
@Input() darkTheme = true;
@Input() roundCropper = false;
@Input() canvasHeight = 400;
@Input() resizeToWidth: number | any;
@Input() resizeToHeight: number | any;
@Input() imageSmoothingEnabled = true;
@Input() imageSmoothingQuality: ImageSmoothingQuality = 'high';
@Input() format: string | any;
@Input() closeBtnText = 'Close';
@Input() applyBtnText = 'Apply';
quality = 92;
@Input() set imageQuality(value: number) {
if (value > 0 && value <= 100) {
this.quality = value;
} else {
this.errorEvent.error('Invalid image quality');
}
}
@Output() closeEvent = new EventEmitter<any>();
@Output() imageCroppedEvent = new EventEmitter<PhotoEditCroppedEvent>();
@Output() errorEvent = new EventEmitter<any>();
imageLoaded = false;
isProcessing = false;
@Input() set source(data: File | string | any) {
if (data instanceof Event) {
// @ts-ignore
const file = (<HTMLInputElement>data.target).files[0];
if (this.FILES_REGEX.test(file.name)) {
if (!this.format) {
// @ts-ignore
this.format = data.target.files[0].type.split('/')[1];
}
const reader = new FileReader();
reader.onload = (ev: any) => {
this.imgUrl = ev.target.result;
};
// @ts-ignore
reader.readAsDataURL(data.target.files[0]);
} else {
console.log('error');
this.errorEvent.emit('Not supported INPUT');
}
} else if (data instanceof File) {
if (this.FILES_REGEX.test(data.name)) {
if (!this.format) {
this.format = data.type.split('/')[1];
}
const reader = new FileReader();
reader.onload = (ev: any) => {
this.imgUrl = ev.target.result;
};
reader.readAsDataURL(data);
} else {
this.errorEvent.emit('Not supported INPUT');
}
} else if (typeof data === 'string') {
if ((/^data:image\/([a-zA-Z]*);base64,([^\"]*)$/).test(data)) {
this.imgUrl = data;
if (!this.format) {
this.format = ((data.split(',')[0]).split(';')[0]).split(':')[1].split('/')[1];
}
} else if (this.isValidImageURL(data)) {
this.imgUrl = data;
if (!this.format) {
// @ts-ignore
this.format = data.split(/[#?]/)[0].split('.').pop().trim();
}
} else {
this.errorEvent.emit('Not supported URL');
}
} else {
this.errorEvent.emit('Not supported INPUT');
}
}
isValidImageURL(str: string) {
return str.match(/^http[^\?]*.(jpg|jpeg|gif|png|tiff|bmp)(\?(.*))?(\#(.*))?$/gim);
}
rotateRight() {
this.cropper.rotate(45);
}
rotateLeft() {
this.cropper.rotate(-45);
}
crop() {
this.cropper.setDragMode('crop');
}
move() {
this.cropper.setDragMode('move');
}
zoomIn() {
this.cropper.zoom(0.1);
}
zoomOut() {
this.cropper.zoom(-0.1);
}
flipH() {
this.cropper.scaleX(-this.cropper.getImageData().scaleX);
}
flipV() {
this.cropper.scaleY(-this.cropper.getImageData().scaleY);
}
reset() {
this.cropper.reset();
}
export() {
this.isProcessing = true;
this.imageLoaded = false;
let cropedImage = this.cropper.getCroppedCanvas({
width: this.resizeToWidth,
height: this.resizeToHeight,
imageSmoothingEnabled: this.imageSmoothingEnabled,
imageSmoothingQuality: this.imageSmoothingQuality
});
const outputImage = cropedImage.toDataURL('image/' + this.format, this.quality);
cropedImage.toBlob((blob: any) => {
this.imageCroppedEvent.emit({
base64: outputImage,
file: new File([blob],
Date.now() + '.' + this.format,
{type: 'image/' + this.format})
});
this.isProcessing = false;
this.imageLoaded = true;
}, 'image/' + this.format, this.quality / 100);
}
close() {
this.closeEvent.emit();
}
onLoad() {
this.image.nativeElement.addEventListener('ready', () => {
this.imageLoaded = true;
});
this.cropper = new Cropper(this.image.nativeElement, {
aspectRatio: this.aspectRatio,
autoCropArea: this.autoCropArea,
autoCrop: this.autoCrop,
modal: this.mask, // black mask
guides: this.guides, // grid
center: this.centerIndicator, // center indicator
viewMode: this.viewMode,
scalable: this.scalable,
zoomable: this.zoomable,
background: false,
cropBoxMovable: this.cropBoxMovable,
cropBoxResizable: this.cropBoxResizable,
});
}
ngOnDestroy(): void {
console.log('destroyed');
}
error() {
this.errorEvent.emit('Error loading image');
}
}
export interface PhotoEditCroppedEvent {
base64?: string;
file?: File;
}
export type imageFormat = 'gif' | 'jpeg' | 'tiff' | 'png' | 'webp' | 'bmp';
*/
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PhotoEditorService } from './photo-editor.service';
describe('PhotoEditorService', () => {
let service: PhotoEditorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PhotoEditorService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,92 @@
/*
import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
EmbeddedViewRef,
Injectable,
Injector, ViewContainerRef
} from '@angular/core';
import {Observable, Subject} from 'rxjs';
import {PhotoEditComponent, PhotoEditCroppedEvent} from './photo-edit.component';
import ViewMode = Cropper.ViewMode;
@Injectable({
providedIn: 'root'
})
export class PhotoEditorService {
private photoEditorSubscriber!: Subject<any>;
private photoEditorComponentRef!: ComponentRef<PhotoEditComponent>;
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private appRef: ApplicationRef,
private injector: Injector) {
}
open(source: Event | string | File | any, options?: Options): Observable<PhotoEditCroppedEvent> {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(PhotoEditComponent);
const componentRef = componentFactory.create(this.injector);
this.appRef.attachView(componentRef.hostView);
const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
this.photoEditorSubscriber = new Subject<string>();
this.photoEditorComponentRef = componentRef;
this.photoEditorComponentRef.instance.closeEvent.subscribe(() => this.close());
this.photoEditorComponentRef.instance.errorEvent.subscribe((data) => this.errorHandler(data));
this.photoEditorComponentRef.instance.imageCroppedEvent.subscribe((data) => this.export(data));
if (options) {
Object.keys(options).map(value => {
// @ts-ignore
this.photoEditorComponentRef.instance[value] = options[value];
})
}
this.photoEditorComponentRef.instance.source = source;
return this.photoEditorSubscriber.asObservable();
}
private errorHandler(data: any) {
this.photoEditorSubscriber.error(data);
this.close();
}
private close() {
this.photoEditorSubscriber.complete();
this.photoEditorComponentRef.destroy();
}
private export(data: any) {
this.photoEditorSubscriber.next(data);
this.close();
}
}
export interface Options {
aspectRatio?: number | any;
modalTitle?: string;
hideModalHeader?: boolean;
autoCropArea?: number;
autoCrop?: boolean;
mask?: boolean;
guides?: boolean;
centerIndicator?: boolean;
viewMode?: ViewMode;
modalMaxWidth?: string;
scalable?: boolean;
zoomable?: boolean;
cropBoxMovable?: boolean;
cropBoxResizable?: boolean;
roundCropper?: boolean;
resizeToWidth?: number | any;
resizeToHeight?: number | any;
imageSmoothingEnabled?: boolean;
imageSmoothingQuality?: ImageSmoothingQuality;
format?: string | any;
imageQuality?: number;
applyBtnText?: string;
closeBtnText?: string;
}
*/