UNPKG

@obliczeniowo/elementary

Version:
604 lines (596 loc) 22.1 kB
import * as i0 from '@angular/core'; import { input, EventEmitter, HostListener, Output, Input, HostBinding, Directive, NgModule, DOCUMENT, Inject, Injectable } from '@angular/core'; import { CommonModule } from '@angular/common'; import * as i1 from '@angular/platform-browser'; import { ElementaryMath } from '@obliczeniowo/elementary/math'; class DragAndDropDirective { sanitizer; elementRef; /** * Change background color */ background = '#eee'; /** * highlight when drag over */ highlightColor = input(); /** * leave color */ leaveColor = input('#ffffff'); /** * Emit DragHandle class object */ files = new EventEmitter(); constructor(sanitizer, elementRef) { this.sanitizer = sanitizer; this.elementRef = elementRef; } onMouseEnter() { this.highlight(this.highlightColor()); } onMouseLeave() { this.highlight(undefined); } highlight(color) { this.elementRef.nativeElement.style.backgroundColor = color || this.leaveColor(); } onDragOver(evt) { evt.preventDefault(); evt.stopPropagation(); this.background = '#999'; } onDragLeave(evt) { evt.preventDefault(); evt.stopPropagation(); this.background = '#eee'; } onDrop(evt) { evt.preventDefault(); evt.stopPropagation(); this.background = '#eee'; const files = []; if (evt.dataTransfer) { for (let i = 0; i < evt.dataTransfer.files.length; i++) { const file = evt.dataTransfer.files[i]; const url = this.sanitizer.bypassSecurityTrustUrl(window.URL.createObjectURL(file)); files.push({ file, url }); } } if (files.length > 0) { this.files.emit({ files, evt }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DragAndDropDirective, deps: [{ token: i1.DomSanitizer }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.4", type: DragAndDropDirective, isStandalone: false, selector: "[oblDragAndDrop]", inputs: { background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null }, leaveColor: { classPropertyName: "leaveColor", publicName: "leaveColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { files: "files" }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "drop": "onDrop($event)" }, properties: { "style.background": "this.background" } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: DragAndDropDirective, decorators: [{ type: Directive, args: [{ selector: '[oblDragAndDrop]', standalone: false }] }], ctorParameters: () => [{ type: i1.DomSanitizer }, { type: i0.ElementRef }], propDecorators: { background: [{ type: Input }, { type: HostBinding, args: ['style.background'] }], files: [{ type: Output }], onMouseEnter: [{ type: HostListener, args: ['mouseenter'] }], onMouseLeave: [{ type: HostListener, args: ['mouseleave'] }], onDragOver: [{ type: HostListener, args: ['dragover', ['$event']] }], onDragLeave: [{ type: HostListener, args: ['dragleave', ['$event']] }], onDrop: [{ type: HostListener, args: ['drop', ['$event']] }] } }); class FilesModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FilesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.4", ngImport: i0, type: FilesModule, declarations: [DragAndDropDirective], imports: [CommonModule], exports: [DragAndDropDirective] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FilesModule, imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: FilesModule, decorators: [{ type: NgModule, args: [{ declarations: [ DragAndDropDirective ], imports: [ CommonModule ], exports: [ DragAndDropDirective ] }] }] }); /* eslint-disable @typescript-eslint/no-unsafe-argument */ class OblFileService { document; constructor(document) { this.document = document; } readTextFile() { return this.readFile('text'); } readFileAsDataUrl() { return this.readFile('DataURL'); } readFileAsArrayBuffer() { return this.readFile('ArrayBuffer'); } loadJsonFile(url) { return fetch(url).then(resp => resp.json()); } /** * Let you save text file on user device * @param text - text to save * @param textType - default text/plain */ saveTextFile(text, fileName, textType = 'text/plain') { const file = new window.Blob([text], { type: textType }); this.saveBlobFile(file, fileName); } /** * Svg save file * @param svgContent * @param fileName */ saveSvgFile(svgContent, fileName) { this.saveTextFile(svgContent, fileName, 'image/svg+xml'); } /** * saving blob file */ saveBlobFile(blob, fileName) { const downloadAnchor = document.createElement('a'); const fileURL = URL.createObjectURL(blob); downloadAnchor.href = fileURL; downloadAnchor.download = fileName; downloadAnchor.click(); } /** * Create HTMLImageElement with loaded image as Promise */ getImageFromUrl(url) { return new Promise((resolve, _reject) => { const image = document.createElement('img'); image.onload = () => { resolve(image); }; image.src = url; }); } /** * Reading file from user device * @param type - file type * @returns file */ async readFile(type) { return await new Promise((resolve, reject) => { const input = this.document.createElement('input'); input.type = 'file'; input.addEventListener('change', (event) => { const reader = new FileReader(); reader.onload = (e) => { resolve(e.target.result); }; switch (type) { case 'text': reader.readAsText(event.target.files[0]); break; case 'DataURL': reader.readAsDataURL(event.target.files[0]); break; case 'ArrayBuffer': reader.readAsArrayBuffer(event.target.files[0]); break; default: reject(new Error('Undefined type')); } }); input.click(); }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: OblFileService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: OblFileService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: OblFileService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: Document, decorators: [{ type: Inject, args: [DOCUMENT] }] }] }); class JsonFileSystemService { /** * Maximum size of file as json string */ maxSize = 300000; /** * field name */ resourcesField = 'systemResources'; /** * Table of folders/files recovered from localeStorage */ systemResources = []; constructor() { const localStorageResource = localStorage.getItem(this.resourcesField); if (localStorageResource) { this.systemResources = JSON.parse(localStorageResource); } } /** * Saving to localStorage */ save() { localStorage.setItem(this.resourcesField, JSON.stringify(this.systemResources)); } /** * Check if resource can be added * @param name name of resource file or folder * @param path path to resource * @param type type of resource: {@link SystemResourceType} * @param data data storage in resource required for files * @returns true if can be added, false if can't */ checkResource(name, path = 'desktop', type = 'folder', // tslint:disable-next-line: ban-types data) { const find = this.systemResources.find(resource => resource.path === path && resource.name === name && resource.type === type); const findParent = path !== 'desktop' && this.systemResources.find(resource => resource.path) || true; if (find || !findParent || !data && type === 'file') { return false; } return true; } /** * Adding folder resource and update localStorage * @param name name of folder * @param path path to folder that contains resource * @param params external params * @returns true if added, or false fail */ addFolder(name, path = 'desktop', params) { if (!this.checkResource(name, path)) { return false; } this.systemResources.push({ type: 'folder', name, path, x: params?.x || 0, y: params?.y || 0 }); this.save(); return true; } /** * Adding the file to system and update localStorage * @param name name of file * @param data required data of file * @param appName name of app that will open this file * @param path path to folder * @param params optional parameters * @returns true if added, false other way */ addFile(name, // tslint:disable-next-line: ban-types data, appName, path = 'desktop', params) { if (!this.checkResource(name, path, 'file', data)) { return false; } const strData = JSON.stringify(data); if (strData.length > this.maxSize) { return false; } this.systemResources.push({ name, data, path, appName, type: 'file', x: params?.x || 0, y: params?.y || 0 }); this.save(); return true; } /** * Return all resources in chosen path * @param path path to resources folder * @returns return resources storage in folder */ getResources(path = 'desktop') { return this.systemResources.filter(resource => resource.path === path); } /** * Return folder from specific path */ getFolder(path) { const found = this.systemResources.find(resource => resource.name === path); return found || false; } /** * Check if resource can be updated and if so update it * @param resource resource to update */ update(resource, newName) { const index = this.systemResources.findIndex(res => res.path === resource.path && res.name === resource.name && res.type === resource.type); if (index !== -1 && !this.systemResources.find((item, i) => item.path === resource.path && item.type === resource.type && item.name === newName && i !== index)) { if (newName) { resource.name = newName; } this.systemResources[index] = resource; this.save(); } } /** * Check if folder path exist */ pathFolderExist(path) { if (path === 'desktop') { return true; } const folders = path.split('/'); if (folders.length === 1) { return false; } const name = folders.pop(); path = folders.join('/'); return !!this.systemResources.find(item => item.path === path && name === item.name); } /** * Delete */ delete(resource, saving = true) { if (resource.type === 'folder') { this.getResources(resource.path + '/' + resource.name).forEach(res => this.delete(res, false)); } this.systemResources = this.systemResources.filter(res => !(res.name === resource.name && res.type === resource.type && res.path === resource.path)); if (saving) { this.save(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: JsonFileSystemService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: JsonFileSystemService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: JsonFileSystemService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class Csv { hasHeaders; separator; /** * Static constructor that create Csv object from array * @param array array to convert to Csv object * @returns Csv object */ static fromArray(array, headers) { const csv = new Csv(!!headers?.length); array.forEach((row) => { row.forEach((ceil, pos) => { const name = headers?.length && headers[pos] || pos; csv.table.set(name, [...(csv.table.get(name) || []), ceil]); }); }); return csv; } /** * Map object of columns containing array of values. * * By default system is parsing values to number or live as string. You can use columnParse field * to parse to any type you wanted (data, pare by JSON, or any other object) */ table = new Map(); /** * errors to throw: * columns - if true throw error if number of columns is not equal in every row * headerNotAString - throw error when one of headers fields is not a string * empty - throw error when file contain empty string * columnParse - error when column contain improper format * * @example * * const file = await this.file.readTextFile(); * * const csv = new Csv(); * * csv.throwing.columnsParse['age'] = (value: string) => { * if (!ElementaryMath.isNumeric(value, true)) { * throw new Error(`Some value in "col 2" is not a number: "${value}"`); * } * }; * * csv.throwing.columnsParse['zip'] = (value: string) => { * if (!ElementaryMath.isNumeric(value, true)) { * throw new Error(`Some value in "zip" is not a number: "${value}"`); * } * * if (!/^[0-9]{5,5}$/.test(value)) { * throw new Error(`Some value in "zip" have not 5 digits: "${value}"`); * } * } * * csv.parse(file, true); * * console.log(csv.table); */ throwing = { columns: true, headerNotAString: false, empty: true, columnsParse: {} }; /** * let you parse column however you want by adding key name corresponding to column name and parsing function * * @example * * const file = await this.file.readTextFile(); * * const csv = new Csv(); * * csv.columnsParse['zip'] = (value: string) => value.substring(0, 2) + '-' + value.substring(2); * csv.columnsParse['date'] = (value: string) => new Date(value); * * csv.parse(file, true); * * console.log(csv.table); */ columnsParse = {}; /** * let you prepare column to export to CSV clear format by using key name of column with method * that revers parsing method * * WARNING! If you use columnsParse you probably want to use this one as reverse process to CSV */ columnsPrepare = {}; /** * Default so call do nothing parsing string global method to override * * You can use parsing string static method from @obliczeniowo/elementary/parse call ParseString * or use totally own one * * @value - parsed string value */ parseString = (value) => value; /** * Reverse method of parseString used to export back to CSV */ prepareString = (value) => value.toString(); constructor(hasHeaders = false, separator = ';') { this.hasHeaders = hasHeaders; this.separator = separator; } headers() { return Array.from(this.table.keys()); } getRawRow(index) { const line = []; this.table.forEach(column => { line.push(column[index]); }); return line; } getJsonRow(index) { const line = {}; this.table.forEach((column, key) => { line[key] = column[index]; }); return line; } getColumn(name) { const value = this.table.get(name); if (!value) { throw new Error(`Column "${name}" not exist`); } return value; } parse(csvText, hasColumns = this.hasHeaders, separator = this.separator) { // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (this.throwing.empty && (!csvText || !csvText.length)) { throw new Error('CSV file is empty'); } let rows = csvText.search('\r\n') !== -1 && csvText.split('\r\n') || csvText.split('\n'); rows = rows.filter(row => row !== ''); let headers = []; if (hasColumns) { headers = rows.shift()?.split(separator) || []; if (headers.length && this.throwing.headerNotAString) { const notNumber = headers.find(header => ElementaryMath.isNumeric(header, true)); if (notNumber) { throw new Error(`Column name: ${notNumber} is a number`); } } } let length = headers.length; rows.forEach((row, rowIndex) => { const values = row.split(separator); if (!length) { length = values.length; } if (this.throwing.columns && length !== values.length) { throw new Error(`Error in line ${headers.length ? rowIndex + 2 : rowIndex + 1} of CSV file. Number of fields not equal to previous one`); } values.forEach((value, index) => { const id = headers[index] || index; const column = this.table.get(id) || []; if (this.throwing.columnsParse[id]) { this.throwing.columnsParse[id](value); } value = this.parseString(value); column.push( // eslint-disable-next-line @typescript-eslint/prefer-optional-chain this.columnsParse[id] && this.columnsParse[id](value) || ElementaryMath.isNumeric(value, true) && parseFloat(value) || value); this.table.set(id, column); }); }); } prepare() { let csv = ''; const headers = this.headers(); if (!headers.length) { throw new Error('Nothing to save'); } if (this.hasHeaders) { csv += headers.join(this.separator) + '\n'; } const length = this.table.get(headers[0])?.length || 0; for (let row = 0; row < length; row++) { const record = []; this.table.forEach((column, key) => record.push( // eslint-disable-next-line @typescript-eslint/prefer-optional-chain this.columnsPrepare[key] && this.columnsPrepare[key](column[row]) || this.prepareString(column[row]))); csv += record.join(this.separator) + '\n'; } return csv; } toJson() { const jsonObj = []; const headers = this.headers(); if (!headers.length) { throw new Error('Nothing to save'); } const length = this.table.get(headers[0])?.length || 0; for (let row = 0; row < length; row++) { const obj = {}; this.table.forEach((column, key) => obj[key] = // eslint-disable-next-line @typescript-eslint/prefer-optional-chain this.columnsPrepare[key] && this.columnsPrepare[key](column[row]) || this.prepareString(column[row])); jsonObj.push(obj); } return jsonObj; } } /** * Generated bundle index. Do not edit. */ export { Csv, DragAndDropDirective, FilesModule, JsonFileSystemService, OblFileService }; //# sourceMappingURL=obliczeniowo-elementary-files.mjs.map