import {Component, Input, OnInit} from '@angular/core'; import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {HelperService} from 'gp-admin-abstract'; import {ImageUploadModel} from './image-upload.model'; @Component({ selector: 'gp-image-upload', template: `
`, styles: [ `@charset "UTF-8"; /** * Переменные */ :root { --color-white: white; --color-success: #00afec; --color-warning: #ffb827; --color-error: #f35252; --color-disabled: #dbdbdb; --color-black: black; --color-span: #b5b5b5; --color-span-second: #212121; --color-bg: #f8f8f8; --color-bg-second: #f8f8f8; --color-bg-success: rgba(0, 175, 236, 0.1); --color-bg-warning: var(--color-warning); --color-bg-error: var(--color-error); --color-placeholder: #b5b5b5; --color-border: #dbdbdb; --color-radio-border: #d3d3d3; --font-roboto: 'Roboto', sans-serif; --font-helvetica: 'Helvetica', sans-serif; } /** * */ /** * Стили для компонента "app-realty-image-list" */ .img-uploader { font-family: "Roboto", sans-serif; margin-top: .8rem; margin-bottom: .8rem; } .img-uploader--loaded .img-uploader__label:hover { cursor: default; opacity: 1; } .img-uploader--error .img-uploader__default { background-image: url("/assets/img/camera--error.svg"); border-color: #f35252; } .img-uploader--disabled .img-uploader__default { background-image: url("/assets/img/camera--disabled.svg"); border-color: #b5b5b5; } .img-uploader__label { display: block; cursor: pointer; } .img-uploader__label:hover { opacity: 0.5; } .img-uploader__input { display: none; } .img-uploader__title, .img-uploader__description { font: 500 1.4rem/1.6rem "Roboto", sans-serif; letter-spacing: .03rem; margin-bottom: .3rem; width: 100%; } .img-uploader__title { color: #00afec; } .img-uploader__description { color: #b5b5b5; } .img-uploader__info { align-content: center; margin-left: 1.6rem; } .img-uploader__default { background-image: url("/assets/img/camera.svg"); background-position: center center; background-repeat: no-repeat; border: 0.2rem dotted #00afec; border-radius: .3rem; width: 6rem; height: 6rem; } .img-uploader__img { background-color: #ccc; background-position: center; background-repeat: no-repeat; background-size: contain; border-radius: .3rem; width: 6rem; height: 6rem; position: relative; margin-right: 1.6rem; } .img-uploader__delete { background-color: rgba(255, 255, 255, 0.8); width: 2.5rem; height: 2.5rem; position: absolute; top: 0; right: 0; border-radius: 0 0 0 3rem; cursor: pointer; } .img-uploader__delete:before, .img-uploader__delete:after { content: ""; display: block; width: .2rem; height: 1rem; background-color: #f35252; position: absolute; } .img-uploader__delete:before { -moz-transform: translate(1.5rem, 0.5rem) rotate(45deg); -o-transform: translate(1.5rem, 0.5rem) rotate(45deg); -ms-transform: translate(1.5rem, 0.5rem) rotate(45deg); -webkit-transform: translate(1.5rem, 0.5rem) rotate(45deg); transform: translate(1.5rem, 0.5rem) rotate(45deg); } .img-uploader__delete:after { -moz-transform: translate(15px, 5px) rotate(-45deg); -o-transform: translate(15px, 5px) rotate(-45deg); -ms-transform: translate(15px, 5px) rotate(-45deg); -webkit-transform: translate(15px, 5px) rotate(-45deg); transform: translate(15px, 5px) rotate(-45deg); } .img-uploader__delete:hover { background-color: white; } `], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: ImageUploadComponent, multi: true } ], }) export class ImageUploadComponent implements OnInit, ControlValueAccessor { /** * Список отображемых изображений на форме * * @type {Array} */ internalImages: any[] = []; /** * Список удаленных картинок (полученных с сервера) * * @type {Array} */ deletedImages: string[] = []; /** * Возвращаемое значение */ private returnValue: ImageUploadModel; /** * Заголовок * @type {string} */ @Input() title = ''; /** * Описание * @type {string} */ @Input() description = ''; /** * Количество загружаемых изображений * * @type {number} */ @Input() maxCountFiles: number; /** * Список разрешенных MIME-типов */ @Input() mimeTypes: Array | string = ['image/png', 'image/jpeg', 'image/jpg']; /** * Максимальный размер файла в байтах */ @Input() maxSize: number; /** * Кастомная ошибка * @type {boolean} */ @Input() error: string|boolean = false; /** * Устанавливает активность компонента * * @type {boolean} */ @Input() disabled: boolean = false; /** * Префикс URL для отображения изображений * @type {string} */ @Input() imageUrlPrefix: string; constructor(private sanitizer: DomSanitizer, private helperService: HelperService) { } ngOnInit() { this.returnValue = new ImageUploadModel(); this.internalImages.push({ isLoaded: false, error: null, imageGuid: '', selectedFile: null, selectedFileSrc: null }); } writeValue(value: ImageUploadModel): void { if (value && value.existsGuids) { for (const imageGuid of value.existsGuids) { this.internalImages.push({ isLoaded: true, error: this.error, imageGuid, selectedFile: null, selectedFileSrc: null }); } this.checkForInsert(); this.returnValue.existsGuids = value.existsGuids; } } registerOnChange(fn: any): void { this.propagateChange = fn; } registerOnTouched(fn: any): void { } setDisabledState(value: boolean) { this.disabled = value; } propagateChange = (_: any) => { } /** * Выбрали и загружаем картинку * * @param e * @param index */ onChange(e: any, index: number) { if (!this.disabled) { const el = e.srcElement || e.target; const file = el.files.length > 0 ? el.files[0] : null; if (file) { this.updateModel(file, index); } } } /** * Запрещаем изменение картинки * И если disabled - делаем кнопку неактивной * * @param e * @param index */ onClick(e: any, index: number) { if (this.disabled || this.internalImages[index] && this.internalImages[index].isLoaded) { e.preventDefault(); } } /** * Проверка картинки на загрузку * * @param file * @param index */ private updateModel(file: File, index: number) { if (this.mimeTypes.indexOf(file.type) === -1) { this.internalImages[index].error = 'Неверный тип файла!'; this.internalImages[index].isLoaded = false; } else if (!!this.maxSize && (file.size > this.maxSize)) { const pretty = this.helperService.filesize(this.maxSize); this.internalImages[index].error = `Размер превышает ${pretty}`; this.internalImages[index].isLoaded = false; } else { this.internalImages[index] = { isLoaded: true, error: false, selectedFile: file, selectedFileSrc: URL.createObjectURL(file) }; this.propagateChange(this.returnValues()); this.checkForInsert(); } } /** * Удалить загруженную картинку * * @param e * @param index */ deleteLoadedImage(e: Event, index: number) { e.preventDefault(); if (this.internalImages[index].imageGuid) { this.deletedImages.push(this.internalImages[index].imageGuid); } this.internalImages.splice(index, 1); this.propagateChange(this.returnValues()); this.checkForInsert(); } reset() { this.internalImages = []; this.deletedImages = []; this.returnValue = new ImageUploadModel(); this.internalImages.push({ isLoaded: false, error: null, imageGuid: '', selectedFile: null, selectedFileSrc: null }); } /** * Получить загруженную картинку и установить ее отображение * * @returns {SafeStyle} */ public imageSrc(index: number): SafeStyle { const src = (this.internalImages[index] && this.internalImages[index].selectedFile) ? this.internalImages[index].selectedFileSrc : this.imageUrlPrefix + this.internalImages[index].imageGuid; return this.sanitizer.bypassSecurityTrustStyle('url(' + src + ')'); } /** * Получить разрешенные типы картинки * * @returns {any} */ get acceptMimeTypes(): string { return (this.mimeTypes instanceof Array) ? this.mimeTypes.join(',') : this.mimeTypes; } /** * Проверка на отображение кнопки "добавления фото" */ private checkForInsert() { const alreadyShowInsert = this.internalImages.find(f => f.isLoaded === false), currentLength = alreadyShowInsert ? this.internalImages.length - 1 : this.internalImages.length; if (!alreadyShowInsert && (currentLength < this.maxCountFiles)) { this.internalImages.push({ isLoaded: false, error: this.error, imageGuid: null, selectedFile: null, selectedFileSrc: null }); } else if (alreadyShowInsert && (currentLength === this.maxCountFiles)) { const index = this.internalImages.indexOf(alreadyShowInsert); this.internalImages.splice(index, 1); } } /** * Возвращаемое значение * * @returns {Object} */ private returnValues() { const uploadedImages = this.internalImages .filter(f => f.isLoaded === true && f.selectedFile) .map((f) => f.selectedFile); this.returnValue.addedFiles = uploadedImages; this.returnValue.deletedGuids = this.deletedImages; return this.returnValue; } }