@obliczeniowo/elementary
Version:
Library made in Angular version 20
604 lines (596 loc) • 22.1 kB
JavaScript
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