From ab94927c79aaf1a35295c1cae04c7035cbe2b2de Mon Sep 17 00:00:00 2001 From: Alexandr Date: Fri, 2 Oct 2020 15:29:40 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/async/asyncTimeout.ts | 5 ++ src/async/cachedPromise.ts | 31 ++++++++++ src/async/promisifyAPI.ts | 23 ++++++++ src/calculateScrollBottom.ts | 3 + src/date/index.ts | 57 +++++++++++++++++++ src/decorators/eventValue.ts | 10 ++++ src/decorators/preventDefault.ts | 8 +++ src/decorators/stopPropagation.ts | 8 +++ src/files/blobToFile.ts | 6 ++ src/files/toBase64.ts | 7 +++ src/index.ts | 18 +++--- src/isIe.ts | 15 +++++ src/list/htmlCollectionToArray.ts | 3 + src/list/multiGroupBy.ts | 16 ++++++ src/localStorage/dataManager.ts | 26 +++++++++ src/localStorage/localStorageService.ts | 13 +++++ src/object/isDeepEqual.ts | 29 ++++++++++ src/object/parseJsonSelf.ts | 10 ++++ src/object/path.ts | 12 ++++ src/object/smartDeepMergeRight.ts | 20 +++++++ src/replacementRamda/customNot.ts | 5 ++ src/replacementRamda/customRamdaFilter.ts | 5 ++ src/replacementRamda/returnBoolean.ts | 5 ++ src/request/decoders/booleanFieldDecoder.ts | 11 ++++ .../decoders/codeTitileFieldDecoder.ts | 13 +++++ src/request/decoders/constValueDecoder.ts | 4 ++ src/request/decoders/enumDecoder.ts | 23 ++++++++ src/request/decoders/falseValueDecoder.ts | 6 ++ .../decoders/fieldWithDefaultDecoder.ts | 15 +++++ src/request/decoders/identityValueDecoder.ts | 4 ++ src/request/decoders/momentFieldDecoder.ts | 14 +++++ src/request/decoders/numberFieldDecoder.ts | 37 ++++++++++++ src/request/decoders/orNull.ts | 12 ++++ src/request/decoders/toStringDecoder.ts | 11 ++++ src/request/decoders/trueValueDecoder.ts | 6 ++ src/request/decoders/valueDecoder.ts | 7 +++ src/string/capitalizeFirstStringCharacter.ts | 3 + src/string/nbsp.ts | 1 + src/string/searchInString.ts | 3 + src/string/wordDeclination.ts | 5 ++ src/types/DecoderType.ts | 2 + src/types/Ramda.ts | 5 ++ 42 files changed, 508 insertions(+), 9 deletions(-) create mode 100644 src/async/asyncTimeout.ts create mode 100644 src/async/cachedPromise.ts create mode 100644 src/async/promisifyAPI.ts create mode 100644 src/calculateScrollBottom.ts create mode 100644 src/date/index.ts create mode 100644 src/decorators/eventValue.ts create mode 100644 src/decorators/preventDefault.ts create mode 100644 src/decorators/stopPropagation.ts create mode 100644 src/files/blobToFile.ts create mode 100644 src/files/toBase64.ts create mode 100644 src/isIe.ts create mode 100644 src/list/htmlCollectionToArray.ts create mode 100644 src/list/multiGroupBy.ts create mode 100644 src/localStorage/dataManager.ts create mode 100644 src/localStorage/localStorageService.ts create mode 100644 src/object/isDeepEqual.ts create mode 100644 src/object/parseJsonSelf.ts create mode 100644 src/object/path.ts create mode 100644 src/object/smartDeepMergeRight.ts create mode 100644 src/replacementRamda/customNot.ts create mode 100644 src/replacementRamda/customRamdaFilter.ts create mode 100644 src/replacementRamda/returnBoolean.ts create mode 100644 src/request/decoders/booleanFieldDecoder.ts create mode 100644 src/request/decoders/codeTitileFieldDecoder.ts create mode 100644 src/request/decoders/constValueDecoder.ts create mode 100644 src/request/decoders/enumDecoder.ts create mode 100644 src/request/decoders/falseValueDecoder.ts create mode 100644 src/request/decoders/fieldWithDefaultDecoder.ts create mode 100644 src/request/decoders/identityValueDecoder.ts create mode 100644 src/request/decoders/momentFieldDecoder.ts create mode 100644 src/request/decoders/numberFieldDecoder.ts create mode 100644 src/request/decoders/orNull.ts create mode 100644 src/request/decoders/toStringDecoder.ts create mode 100644 src/request/decoders/trueValueDecoder.ts create mode 100644 src/request/decoders/valueDecoder.ts create mode 100644 src/string/capitalizeFirstStringCharacter.ts create mode 100644 src/string/nbsp.ts create mode 100644 src/string/searchInString.ts create mode 100644 src/string/wordDeclination.ts create mode 100644 src/types/Ramda.ts diff --git a/src/async/asyncTimeout.ts b/src/async/asyncTimeout.ts new file mode 100644 index 0000000..687c490 --- /dev/null +++ b/src/async/asyncTimeout.ts @@ -0,0 +1,5 @@ +export function asyncTimeout(timeout: number, value?: T) { + return new Promise((resolve) => { + setTimeout(resolve, timeout, value); + }); +} diff --git a/src/async/cachedPromise.ts b/src/async/cachedPromise.ts new file mode 100644 index 0000000..57b7636 --- /dev/null +++ b/src/async/cachedPromise.ts @@ -0,0 +1,31 @@ +export function getPromiseCacheFunc(seconds = 10) { + const storage = new Map }>(); + + function getNowSeconds() { + return new Date().getTime() / 1000; + } + + function add(key: any, result: any) { + storage.set(key, { + promiseResult: result, + expiredTime: getNowSeconds() + seconds, + }); + } + + function isExpired(key: any) { + const expiredTime = storage.get(key)?.expiredTime; + return expiredTime && getNowSeconds() > expiredTime; + } + + return function (promise: () => Promise, key: string): () => Promise { + if (!storage.has(key) || isExpired(key)) { + return () => + promise().then((promiseResult) => { + add(key, promiseResult); + return promiseResult; + }); + } + const promiseResult = storage.get(key)?.promiseResult; + return () => Promise.resolve(promiseResult); + }; +} diff --git a/src/async/promisifyAPI.ts b/src/async/promisifyAPI.ts new file mode 100644 index 0000000..1c39262 --- /dev/null +++ b/src/async/promisifyAPI.ts @@ -0,0 +1,23 @@ +export function promisifyAPI( + api: () => Promise, + states: { + stateStart?: () => any; + stateSuccess?: (data: T) => any; + stateError?: (data: any) => any; + }, + emitReject = false, +): Promise { + return new Promise((resolve, reject) => { + if (states.stateStart) states.stateStart(); + api().then( + (data) => { + if (states.stateSuccess) states.stateSuccess(data); + resolve(data); + }, + (error) => { + if (states.stateError) states.stateError(error); + if (emitReject) reject(error); + }, + ); + }); +} diff --git a/src/calculateScrollBottom.ts b/src/calculateScrollBottom.ts new file mode 100644 index 0000000..2731b43 --- /dev/null +++ b/src/calculateScrollBottom.ts @@ -0,0 +1,3 @@ +export const calculateScrollBottom = (element: HTMLElement) => { + return element.scrollHeight - element.scrollTop - element.clientHeight; +}; diff --git a/src/date/index.ts b/src/date/index.ts new file mode 100644 index 0000000..3b507fc --- /dev/null +++ b/src/date/index.ts @@ -0,0 +1,57 @@ +import "moment/locale/ru"; +import moment, { LocaleSpecification, Moment, unitOfTime } from "moment"; +import { sort, uniqWith } from "ramda"; + +export enum DateMode { + DAY_MONTH_YEAR = "DD MMMM YYYY", + DAY_MONTH_YEAR_TIME = "DD MMMM YYYY HH:mm", + DAY_MONTH = "DD MMMM", + DAY_MONTH_SHORT = "DD.MM", + DATE = "DD.MM.YYYY", + DATE_WITH_SLASHES = "DD/MM/YYYY", + UTC = "YYYY.MM.DD HH:mm:ss UTC-0000", + UTC_PROXY_SERVER = "YYYY-MM-DDTHH:mm:ss", + TIME = "HH:mm", + TIME_WITH_SECONDS = "HH:mm:ss", + DATE_TIME = "DD.MM.YYYY HH:mm", + DATE_TIME_WITH_SECONDS = "DD.MM.YYYY HH:mm:ss", + DATE_TIME_WITH_MILLI_SECONDS = "DD.MM.YYYY HH:mm:ss:SSS", + YEAR_AND_QUARTER = "YYYY.Q", + SERVER_YEAR_AND_QUARTER = "YYYY~Q", + HUMANABLE_DATE_TIME_WITHOUT_YEAR = "D MMMM в HH:mm", +} + +// @ts-ignore +const config = moment.localeData("ru")["_config"] as LocaleSpecification; + +const weekdaysShort: string[] = config.weekdaysShort as any; + +moment.updateLocale("ru", { + calendar: { + ...config.calendar, + }, + week: { + dow: 1, + }, + weekdaysShort: [weekdaysShort[1], ...weekdaysShort.slice(2), weekdaysShort[0]], +}); + +export function momentFromLocalString(value: string, mode: DateMode = DateMode.DATE) { + return moment(value, mode); +} + +export const sortDates = (dates: Moment[]): Moment[] => sort((a: Moment, b: Moment) => a.diff(b), dates); + +export const uniqDatesBy = (by?: unitOfTime.StartOf) => (dates: Moment[]): Moment[] => + uniqWith((prevDate, currDate) => prevDate.isSame(currDate, by), dates); + +export const currentDateIsBetweenDates = (start: Moment, end: Moment) => moment().isBetween(start, end); + +export function convertMomentToStartDateModeInterval(date: Moment, mode: DateMode) { + return moment(date.format(mode), mode); +} + +export const changeFormatMoment = (initialFormat: DateMode, convertFormat: DateMode) => (date: Moment) => + moment(date.format(initialFormat), convertFormat); + +export const now = moment(); diff --git a/src/decorators/eventValue.ts b/src/decorators/eventValue.ts new file mode 100644 index 0000000..127bbe0 --- /dev/null +++ b/src/decorators/eventValue.ts @@ -0,0 +1,10 @@ +export default (func: Function) => { + return function (ev?: any) { + if (ev && ev.target) { + func(ev.target.value); + return; + } + + func(null); + }; +}; diff --git a/src/decorators/preventDefault.ts b/src/decorators/preventDefault.ts new file mode 100644 index 0000000..14e099f --- /dev/null +++ b/src/decorators/preventDefault.ts @@ -0,0 +1,8 @@ +export default (func?: Function) => { + return function (ev?: any) { + if (ev) { + ev.preventDefault(); + } + func && func(ev); + }; +}; diff --git a/src/decorators/stopPropagation.ts b/src/decorators/stopPropagation.ts new file mode 100644 index 0000000..2834a36 --- /dev/null +++ b/src/decorators/stopPropagation.ts @@ -0,0 +1,8 @@ +export default (func?: Function) => { + return function (ev?: any) { + if (ev) { + ev.stopPropagation(); + } + if (func) func(ev); + }; +}; diff --git a/src/files/blobToFile.ts b/src/files/blobToFile.ts new file mode 100644 index 0000000..7e78bb2 --- /dev/null +++ b/src/files/blobToFile.ts @@ -0,0 +1,6 @@ +export function blobToFile(theBlob: Blob, fileName: string): File { + const blob: any = new Blob([theBlob]); + blob.lastModifiedDate = new Date(); + blob.name = fileName; + return blob as File; +} \ No newline at end of file diff --git a/src/files/toBase64.ts b/src/files/toBase64.ts new file mode 100644 index 0000000..2db0c12 --- /dev/null +++ b/src/files/toBase64.ts @@ -0,0 +1,7 @@ +export const toBase64 = (file: File) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = (error) => reject(error); + }); diff --git a/src/index.ts b/src/index.ts index 7824841..5c215a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,19 +4,19 @@ export * from "./files/bytesToHumanReadableFormat"; export * from "./files/createFileInput"; export * from "./types/DecoderType"; export * from "./types/FileInterface"; -export * from "./request/defaultDecoders"; +export * from "./request/decoders/numberFieldDecoder"; export * from "./request/index"; -export * from "./asyncTimeout"; +export * from "./async/asyncTimeout"; export * from "./BaseError"; -export * from "./capitalizeFirstStringCharacter"; +export * from "./string/capitalizeFirstStringCharacter"; export * from "./date"; export * from "./events"; -export * from "./htmlCollectionToArray"; +export * from "./list/htmlCollectionToArray"; export * from "./is"; -export * from "./isDeepEqual"; +export * from "./object/isDeepEqual"; export * from "./linkIsNative"; -export * from "./nbsp"; -export * from "./path"; -export * from "./promisifyAPI"; -export * from "./searchInString"; +export * from "./string/nbsp"; +export * from "./object/path"; +export * from "./async/promisifyAPI"; +export * from "./string/searchInString"; export * from "./state/loadingContainer"; diff --git a/src/isIe.ts b/src/isIe.ts new file mode 100644 index 0000000..09b6e11 --- /dev/null +++ b/src/isIe.ts @@ -0,0 +1,15 @@ +export default function detectIE() { + const ua = window.navigator.userAgent; + + const msie = ua.indexOf("MSIE "); + if (msie > 0) { + return true; + } + + const trident = ua.indexOf("Trident/"); + if (trident > 0) { + return true; + } + + return false; +} diff --git a/src/list/htmlCollectionToArray.ts b/src/list/htmlCollectionToArray.ts new file mode 100644 index 0000000..6fee536 --- /dev/null +++ b/src/list/htmlCollectionToArray.ts @@ -0,0 +1,3 @@ +export function htmlCollectionToArray(collection: HTMLCollection) { + return Array.of(...collection) as HTMLElement[]; +} diff --git a/src/list/multiGroupBy.ts b/src/list/multiGroupBy.ts new file mode 100644 index 0000000..ff1c843 --- /dev/null +++ b/src/list/multiGroupBy.ts @@ -0,0 +1,16 @@ +import { curry } from "ramda"; + +type FormattedObjectType = { [key: string]: T[] }; + +export const multiGroupBy = curry(function ( + getGroupKeys: (el: T) => (string | number)[], + list: T[], +): FormattedObjectType { + return [...list].reduce>((acc, el) => { + const groupKeys = getGroupKeys(el); + groupKeys.forEach((key) => { + if (key) acc[key] = acc[key] ? [...acc[key], el] : [el]; + }); + return acc; + }, {}); +}); diff --git a/src/localStorage/dataManager.ts b/src/localStorage/dataManager.ts new file mode 100644 index 0000000..a53d6a4 --- /dev/null +++ b/src/localStorage/dataManager.ts @@ -0,0 +1,26 @@ +import { compose, curry } from "ramda"; + +export interface DataManagerInterface { + setData: (key: string, elements: any[]) => void; + getData: (key: string) => any; +} + +export class DataManager { + private inner: DataManagerInterface; + + constructor(inner: DataManagerInterface) { + this.inner = inner; + } + + setData = (keyName: string, elements: any[]) => { + return this.inner.setData(keyName, elements); + }; + + getData = (name: string) => { + return this.inner.getData(name); + }; + + map = (key: string, cb: (value: any) => any): void => { + compose(curry(this.inner.setData)(key), cb, this.inner.getData)(key); + }; +} diff --git a/src/localStorage/localStorageService.ts b/src/localStorage/localStorageService.ts new file mode 100644 index 0000000..3a74647 --- /dev/null +++ b/src/localStorage/localStorageService.ts @@ -0,0 +1,13 @@ +import { DataManagerInterface } from "./dataManager"; + +export class LocalStorageService implements DataManagerInterface { + setData = (key: string, value: any) => { + localStorage.setItem(key, JSON.stringify(value)); + }; + + getData = (key: string) => { + const gotData = localStorage.getItem(key); + if (gotData === null || gotData === undefined) return null; + return JSON.parse(gotData); + }; +} diff --git a/src/object/isDeepEqual.ts b/src/object/isDeepEqual.ts new file mode 100644 index 0000000..934c7dc --- /dev/null +++ b/src/object/isDeepEqual.ts @@ -0,0 +1,29 @@ +export function isDeepEqual(objA: any, objB: any) { + if (Object.is(objA, objB)) return true; + + if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) return false; + + if (objA.prototype !== objB.prototype) return false; + + const keysA = Object.keys(objA); + const keysB = Object.keys(objB); + if (keysA.length !== keysB.length) return false; + + // @ts-ignore + const bHasOwnProperty = hasOwnProperty.bind(objB); + while (keysA.length > 0) { + const key: any = keysA.pop(); + + if (!bHasOwnProperty(key)) return false; + + const a = objA[key]; + const b = objB[key]; + if (!Object.is(a, b)) { + if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false; + + if (!isDeepEqual(a, b)) return false; + } + } + + return true; +} diff --git a/src/object/parseJsonSelf.ts b/src/object/parseJsonSelf.ts new file mode 100644 index 0000000..6658339 --- /dev/null +++ b/src/object/parseJsonSelf.ts @@ -0,0 +1,10 @@ +import { NullableType } from ".."; + +export function parseJsonSelf(param: string, callbackError: (error: Error) => void): NullableType { + try { + return JSON.parse(param) as T; + } catch (error) { + callbackError(error); + return null; + } +} diff --git a/src/object/path.ts b/src/object/path.ts new file mode 100644 index 0000000..d6cbd28 --- /dev/null +++ b/src/object/path.ts @@ -0,0 +1,12 @@ +import { path as ramdaPath } from "ramda"; + +import { isString } from "../is"; + +export function splitByPoint(path: string) { + return path.split("."); +} + +export function path(fullPath: string | string[], obj: any): any { + if (isString(fullPath)) return ramdaPath(splitByPoint(fullPath), obj); + return ramdaPath(fullPath, obj); +} diff --git a/src/object/smartDeepMergeRight.ts b/src/object/smartDeepMergeRight.ts new file mode 100644 index 0000000..59c75a7 --- /dev/null +++ b/src/object/smartDeepMergeRight.ts @@ -0,0 +1,20 @@ +import { mergeWithKey } from "ramda"; + +function isObject(x: any) { + return Object.prototype.toString.call(x) === "[object Object]"; +} + +export const smartDeepMergeRight = (deepLevelThreshold: number) => { + return function mergeDeepWithKey(lObj: any, rObj: any, _level = 1): any { + return mergeWithKey( + (_, lVal, rVal) => { + if (isObject(lVal) && isObject(rVal) && _level < deepLevelThreshold) { + return mergeDeepWithKey(lVal, rVal, _level + 1); + } + return rVal; + }, + lObj, + rObj, + ); + }; +}; diff --git a/src/replacementRamda/customNot.ts b/src/replacementRamda/customNot.ts new file mode 100644 index 0000000..bbd434e --- /dev/null +++ b/src/replacementRamda/customNot.ts @@ -0,0 +1,5 @@ +export const customNot = function (cb: (el?: any) => any) { + return function (data: any): boolean { + return !cb(data); + }; +}; diff --git a/src/replacementRamda/customRamdaFilter.ts b/src/replacementRamda/customRamdaFilter.ts new file mode 100644 index 0000000..d4710ef --- /dev/null +++ b/src/replacementRamda/customRamdaFilter.ts @@ -0,0 +1,5 @@ +export const customRamdaFilter = function (cb: (el: T) => boolean) { + return function (list: T[]) { + return list.filter(cb); + }; +}; diff --git a/src/replacementRamda/returnBoolean.ts b/src/replacementRamda/returnBoolean.ts new file mode 100644 index 0000000..aab144c --- /dev/null +++ b/src/replacementRamda/returnBoolean.ts @@ -0,0 +1,5 @@ +export const returnBoolean = function (cb: (el?: any) => any) { + return function (data: any): boolean { + return !!cb(data); + }; +}; diff --git a/src/request/decoders/booleanFieldDecoder.ts b/src/request/decoders/booleanFieldDecoder.ts new file mode 100644 index 0000000..b0f6b19 --- /dev/null +++ b/src/request/decoders/booleanFieldDecoder.ts @@ -0,0 +1,11 @@ +import Decoder from "jsonous"; +import { ok } from "resulty"; + +import { numberFieldDecoder } from "./numberFieldDecoder"; + +export const booleanFieldDecoder = (key: string) => { + return new Decoder((inputData) => { + const value = numberFieldDecoder(key, { defaultValue: 0 }).decodeAny(inputData).getOrElseValue(null); + return ok(!!value); + }); +}; diff --git a/src/request/decoders/codeTitileFieldDecoder.ts b/src/request/decoders/codeTitileFieldDecoder.ts new file mode 100644 index 0000000..f524297 --- /dev/null +++ b/src/request/decoders/codeTitileFieldDecoder.ts @@ -0,0 +1,13 @@ +import Decoder, { string, succeed } from "jsonous"; +import { isString } from "../../is"; +import { fieldWithDefaultDecoder } from "./fieldWithDefaultDecoder"; +import { NullableType } from "../.."; + +export function codeTitleFieldDecoder( + code: string | Decoder>, + title: string | Decoder>, +) { + return succeed({}) + .assign("code", isString(code) ? fieldWithDefaultDecoder(code, string, "") : code) + .assign("title", isString(title) ? fieldWithDefaultDecoder(title, string, "") : title); +} diff --git a/src/request/decoders/constValueDecoder.ts b/src/request/decoders/constValueDecoder.ts new file mode 100644 index 0000000..a9c6a11 --- /dev/null +++ b/src/request/decoders/constValueDecoder.ts @@ -0,0 +1,4 @@ +import Decoder from "jsonous"; +import { ok } from "resulty"; + +export const constValueDecoder = (value: any) => new Decoder(() => ok(value)); \ No newline at end of file diff --git a/src/request/decoders/enumDecoder.ts b/src/request/decoders/enumDecoder.ts new file mode 100644 index 0000000..4bec67c --- /dev/null +++ b/src/request/decoders/enumDecoder.ts @@ -0,0 +1,23 @@ +import Decoder from "jsonous"; +import {err, ok} from "resulty"; + +export function enumDecoder(key: string, map: { [key: string]: T }, defaultValue?: T, handler?: (value: any) => T) { + return new Decoder((value) => { + const fieldValue = value[key]; + const valueInMap = map[fieldValue]; + + if (valueInMap === undefined) { + const availableTypesString = JSON.stringify(Object.keys(map)); + + return handler !== undefined + ? ok(handler(fieldValue)) + : defaultValue !== undefined + ? ok(defaultValue) + : err( + `Переданное значение "${fieldValue}" не соответствует ни одному из позволенных [${availableTypesString}].`, + ); + } + + return ok(valueInMap); + }); +} diff --git a/src/request/decoders/falseValueDecoder.ts b/src/request/decoders/falseValueDecoder.ts new file mode 100644 index 0000000..84c7c04 --- /dev/null +++ b/src/request/decoders/falseValueDecoder.ts @@ -0,0 +1,6 @@ +import Decoder from "jsonous"; +import { ok, err } from "resulty"; + +export const falseValueDecoder = new Decoder(function (value) { + return value === false ? ok(false) : err('Ожидалось "false"'); +}); diff --git a/src/request/decoders/fieldWithDefaultDecoder.ts b/src/request/decoders/fieldWithDefaultDecoder.ts new file mode 100644 index 0000000..00985ad --- /dev/null +++ b/src/request/decoders/fieldWithDefaultDecoder.ts @@ -0,0 +1,15 @@ +import Decoder from "jsonous"; +import { isNil } from "ramda"; +import { err, ok } from "resulty"; +import { NullableType } from "../.."; + +export const fieldWithDefaultDecoder = (key: string, decoder: Decoder, defaultValue: NullableType = null) => + new Decoder((value: Record) => { + if (isNil(value[key])) { + return ok(defaultValue); + } + return decoder.decodeAny(value[key]).cata({ + Ok: (value) => ok(value), + Err: (error) => err(error), + }); + }); diff --git a/src/request/decoders/identityValueDecoder.ts b/src/request/decoders/identityValueDecoder.ts new file mode 100644 index 0000000..ba08dbc --- /dev/null +++ b/src/request/decoders/identityValueDecoder.ts @@ -0,0 +1,4 @@ +import Decoder from "jsonous"; +import {ok} from "resulty"; + +export const identityValueDecoder = new Decoder(ok); diff --git a/src/request/decoders/momentFieldDecoder.ts b/src/request/decoders/momentFieldDecoder.ts new file mode 100644 index 0000000..80b77e0 --- /dev/null +++ b/src/request/decoders/momentFieldDecoder.ts @@ -0,0 +1,14 @@ +import { field, maybe, string } from "jsonous"; +import moment, { Moment } from "moment"; +import { isNothing } from "maybeasy"; + +import { DateMode } from "../../date"; + +export function momentFieldDecoder(key: string, mode = DateMode.DATE, defaultValue?: Moment) { + return maybe(field(key, string)).map((maybeValue) => { + defaultValue = defaultValue || moment.invalid(); + if (isNothing(maybeValue)) return defaultValue; + const value = maybeValue.getOrElseValue(null!); + return !value.length ? defaultValue : moment(value, mode); + }); +} diff --git a/src/request/decoders/numberFieldDecoder.ts b/src/request/decoders/numberFieldDecoder.ts new file mode 100644 index 0000000..516d0f1 --- /dev/null +++ b/src/request/decoders/numberFieldDecoder.ts @@ -0,0 +1,37 @@ +import Decoder, { field } from "jsonous"; +import { ok } from "resulty"; +import { isNil } from "ramda"; + +import { identityValueDecoder } from "./identityValueDecoder"; +import { NullableType } from "../../types/DecoderType"; + +export enum NumberFieldNumChecker { + EMPTY_STRING, + NULL, + NULL_AND_EMPTY_STRING, +} + +const numberFieldNumCheckers = { + [NumberFieldNumChecker.EMPTY_STRING]: (value: any, defaultValue: number) => (value === "" ? defaultValue : value), + [NumberFieldNumChecker.NULL]: (value: any, defaultValue: number) => (isNil(value) ? defaultValue : value), + [NumberFieldNumChecker.NULL_AND_EMPTY_STRING]: (value: any, defaultValue: number) => + isNil(value) || value === "" ? defaultValue : value, +}; + +export const numberFieldDecoder = ( + key: string, + data: { + nullEmptyChecker?: NumberFieldNumChecker; + defaultValue?: number; + } = {}, +) => { + return new Decoder>((inputData) => { + const value = field(key, identityValueDecoder).decodeAny(inputData).getOrElseValue(null); + const newValue = numberFieldNumCheckers[data.nullEmptyChecker || NumberFieldNumChecker.NULL_AND_EMPTY_STRING]( + value, + data["defaultValue"] ? data.defaultValue : 0, + ); + + return ok(isNil(newValue) ? null : parseFloat(newValue.toString().replace(",", "."))); + }); +}; diff --git a/src/request/decoders/orNull.ts b/src/request/decoders/orNull.ts new file mode 100644 index 0000000..d9ec22d --- /dev/null +++ b/src/request/decoders/orNull.ts @@ -0,0 +1,12 @@ +import Decoder from "jsonous"; +import {ok} from "resulty"; +import {NullableType} from "../.."; + +export function orNull(decoder: Decoder, defaultValue: NullableType = null) { + return new Decoder((value) => { + return decoder.decodeAny(value).cata({ + Ok: (value) => ok(value), + Err: () => ok(defaultValue), + }); + }); +} diff --git a/src/request/decoders/toStringDecoder.ts b/src/request/decoders/toStringDecoder.ts new file mode 100644 index 0000000..7e4c0a8 --- /dev/null +++ b/src/request/decoders/toStringDecoder.ts @@ -0,0 +1,11 @@ +import Decoder from "jsonous"; +import { isNil } from "ramda"; +import { err, ok } from "resulty"; + +export const toStringDecoder = new Decoder((value) => { + let result = null; + try { + result = value.toString(); + } catch (e) {} + return isNil(result) ? err(`Не удалось привести "${value}" к строковому типу.`) : ok(result); +}); diff --git a/src/request/decoders/trueValueDecoder.ts b/src/request/decoders/trueValueDecoder.ts new file mode 100644 index 0000000..554ae2c --- /dev/null +++ b/src/request/decoders/trueValueDecoder.ts @@ -0,0 +1,6 @@ +import Decoder from "jsonous"; +import { ok, err } from "resulty"; + +export const trueValueDecoder = new Decoder(function (value) { + return value === true ? ok(true) : err('Ожидалось "true"'); +}); diff --git a/src/request/decoders/valueDecoder.ts b/src/request/decoders/valueDecoder.ts new file mode 100644 index 0000000..d328bd3 --- /dev/null +++ b/src/request/decoders/valueDecoder.ts @@ -0,0 +1,7 @@ +import Decoder from "jsonous"; +import { err, ok } from "resulty"; + +export const valueDecoder = (inputValue: T) => + new Decoder(function (value) { + return value === inputValue ? ok(value) : err(`Пришло: "${value}"; Ожидалось "${inputValue}"`); + }); diff --git a/src/string/capitalizeFirstStringCharacter.ts b/src/string/capitalizeFirstStringCharacter.ts new file mode 100644 index 0000000..deff071 --- /dev/null +++ b/src/string/capitalizeFirstStringCharacter.ts @@ -0,0 +1,3 @@ +export function capitalizeFirstStringCharacter(str: string) { + return `${str[0].toUpperCase()}${str.slice(1)}`; +} diff --git a/src/string/nbsp.ts b/src/string/nbsp.ts new file mode 100644 index 0000000..122a2c1 --- /dev/null +++ b/src/string/nbsp.ts @@ -0,0 +1 @@ +export const nbspString = "\u00A0"; diff --git a/src/string/searchInString.ts b/src/string/searchInString.ts new file mode 100644 index 0000000..79a1ff2 --- /dev/null +++ b/src/string/searchInString.ts @@ -0,0 +1,3 @@ +export default function searchInString(inString: string, target: string, min = 0): boolean { + return target.length > min ? inString.trim().toLowerCase().indexOf(target.trim().toLowerCase()) !== -1 : true; +} diff --git a/src/string/wordDeclination.ts b/src/string/wordDeclination.ts new file mode 100644 index 0000000..6a51ae5 --- /dev/null +++ b/src/string/wordDeclination.ts @@ -0,0 +1,5 @@ +const cases = [2, 0, 1, 1, 1, 2]; + +export default function wordDeclination(number: number, titles: string[]): string { + return titles[number % 100 > 4 && number % 100 < 20 ? 2 : cases[number % 10 < 5 ? number % 10 : 5]]; +} diff --git a/src/types/DecoderType.ts b/src/types/DecoderType.ts index 234f439..2dcfcae 100644 --- a/src/types/DecoderType.ts +++ b/src/types/DecoderType.ts @@ -1,3 +1,5 @@ import Decoder from "jsonous/Decoder"; export type DecoderType> = ReturnType["getOrElseValue"]>; + +export type NullableType = T | null; \ No newline at end of file diff --git a/src/types/Ramda.ts b/src/types/Ramda.ts new file mode 100644 index 0000000..0f38d3e --- /dev/null +++ b/src/types/Ramda.ts @@ -0,0 +1,5 @@ +export type Flatten2 = (arg: T[][]) => T[]; +export type Flatten3 = (arg: T[][][]) => T[]; +export type Flatten4 = (arg: T[][][][]) => T[]; + +export type FilterArray = (func: (a: T) => boolean) => (b: T[]) => T[]; From e7f500f0a84a7127cd916e48c5dedbed9eaf0055 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Fri, 2 Oct 2020 16:06:25 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/asyncTimeout.ts | 5 ---- src/capitalizeFirstStringCharacter.ts | 3 --- src/date.ts | 35 --------------------------- src/htmlCollectionToArray.ts | 3 --- src/isDeepEqual.ts | 29 ---------------------- src/nbsp.ts | 1 - src/path.ts | 12 --------- src/promisifyAPI.ts | 23 ------------------ src/request/defaultDecoders.ts | 34 -------------------------- src/searchInString.ts | 3 --- 10 files changed, 148 deletions(-) delete mode 100644 src/asyncTimeout.ts delete mode 100644 src/capitalizeFirstStringCharacter.ts delete mode 100644 src/date.ts delete mode 100644 src/htmlCollectionToArray.ts delete mode 100755 src/isDeepEqual.ts delete mode 100644 src/nbsp.ts delete mode 100644 src/path.ts delete mode 100644 src/promisifyAPI.ts delete mode 100644 src/request/defaultDecoders.ts delete mode 100644 src/searchInString.ts diff --git a/src/asyncTimeout.ts b/src/asyncTimeout.ts deleted file mode 100644 index 687c490..0000000 --- a/src/asyncTimeout.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function asyncTimeout(timeout: number, value?: T) { - return new Promise((resolve) => { - setTimeout(resolve, timeout, value); - }); -} diff --git a/src/capitalizeFirstStringCharacter.ts b/src/capitalizeFirstStringCharacter.ts deleted file mode 100644 index deff071..0000000 --- a/src/capitalizeFirstStringCharacter.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function capitalizeFirstStringCharacter(str: string) { - return `${str[0].toUpperCase()}${str.slice(1)}`; -} diff --git a/src/date.ts b/src/date.ts deleted file mode 100644 index c09a6c4..0000000 --- a/src/date.ts +++ /dev/null @@ -1,35 +0,0 @@ -import "moment/locale/ru"; -import moment, { LocaleSpecification, Moment } from "moment"; -import { sort } from "ramda"; - -// @ts-ignore -const config = moment.localeData("ru")["_config"] as LocaleSpecification; - -const weekdaysShort: string[] = config.weekdaysShort as any; - -moment.updateLocale("ru", { - calendar: { - ...config.calendar, - }, - week: { - dow: 1, - }, - weekdaysShort: [weekdaysShort[1], ...weekdaysShort.slice(2), weekdaysShort[0]], -}); - -export enum DateMode { - DAY_MONTH_YEAR = "DD MMMM YYYY", - DAY_MONTH_YEAR_TIME = "DD MMMM YYYY HH:mm", - DAY_MONTH = "DD MMMM", - DATE = "DD.MM.YYYY", - TIME = "HH:mm", - DATE_TIME = "DD.MM.YYYY HH:mm", - DATE_TIME_WITH_SECONDS = "DD.MM.YYYY HH:mm:ss", - DATE_TIME_WITH_MILLI_SECONDS = "DD.MM.YYYY HH:mm:ss:SSS", - YEAR_AND_QUARTER = "YYYY.Q", - HUMANABLE_DATE_TIME_WITHOUT_YEAR = "D MMMM в HH:mm", -} - -export const sortDates = (dates: Moment[]): Moment[] => sort((a: Moment, b: Moment) => a.diff(b), dates); - -export const now = moment(); diff --git a/src/htmlCollectionToArray.ts b/src/htmlCollectionToArray.ts deleted file mode 100644 index 6fee536..0000000 --- a/src/htmlCollectionToArray.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function htmlCollectionToArray(collection: HTMLCollection) { - return Array.of(...collection) as HTMLElement[]; -} diff --git a/src/isDeepEqual.ts b/src/isDeepEqual.ts deleted file mode 100755 index 934c7dc..0000000 --- a/src/isDeepEqual.ts +++ /dev/null @@ -1,29 +0,0 @@ -export function isDeepEqual(objA: any, objB: any) { - if (Object.is(objA, objB)) return true; - - if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) return false; - - if (objA.prototype !== objB.prototype) return false; - - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); - if (keysA.length !== keysB.length) return false; - - // @ts-ignore - const bHasOwnProperty = hasOwnProperty.bind(objB); - while (keysA.length > 0) { - const key: any = keysA.pop(); - - if (!bHasOwnProperty(key)) return false; - - const a = objA[key]; - const b = objB[key]; - if (!Object.is(a, b)) { - if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false; - - if (!isDeepEqual(a, b)) return false; - } - } - - return true; -} diff --git a/src/nbsp.ts b/src/nbsp.ts deleted file mode 100644 index 122a2c1..0000000 --- a/src/nbsp.ts +++ /dev/null @@ -1 +0,0 @@ -export const nbspString = "\u00A0"; diff --git a/src/path.ts b/src/path.ts deleted file mode 100644 index 15c78bd..0000000 --- a/src/path.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { path as ramdaPath } from "ramda"; - -import { isString } from "./is"; - -export function splitByPoint(path: string) { - return path.split("."); -} - -export function path(fullPath: string | string[], obj: any): any { - if (isString(fullPath)) return ramdaPath(splitByPoint(fullPath), obj); - return ramdaPath(fullPath, obj); -} diff --git a/src/promisifyAPI.ts b/src/promisifyAPI.ts deleted file mode 100644 index 1c39262..0000000 --- a/src/promisifyAPI.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function promisifyAPI( - api: () => Promise, - states: { - stateStart?: () => any; - stateSuccess?: (data: T) => any; - stateError?: (data: any) => any; - }, - emitReject = false, -): Promise { - return new Promise((resolve, reject) => { - if (states.stateStart) states.stateStart(); - api().then( - (data) => { - if (states.stateSuccess) states.stateSuccess(data); - resolve(data); - }, - (error) => { - if (states.stateError) states.stateError(error); - if (emitReject) reject(error); - }, - ); - }); -} diff --git a/src/request/defaultDecoders.ts b/src/request/defaultDecoders.ts deleted file mode 100644 index 30cde97..0000000 --- a/src/request/defaultDecoders.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Decoder, { field, maybe, string } from "jsonous"; -import { err, ok } from "resulty"; -import { isNil } from "ramda"; -import moment, { Moment } from "moment"; -import { isNothing } from "maybeasy"; - -import { DateMode } from "../date"; - -export const identityValueDecoder = new Decoder(ok); - -export const valueDecoder = (inputValue: T) => - new Decoder(function (value) { - return value === inputValue ? ok(value) : err(`Пришло: "${value}"; Ожидалось "${inputValue}"`); - }); - -export const fieldWithDefaultDecoder = (key: string, decoder: Decoder, defaultValue: A | null = null) => - new Decoder((value: Record) => { - if (isNil(value[key])) { - return ok(defaultValue); - } - return decoder.decodeAny(value[key]).cata({ - Ok: (value) => ok(value), - Err: (error) => err(error), - }); - }); - -export function momentFieldDecoder(key: string, mode = DateMode.DATE, defaultValue?: Moment) { - return maybe(field(key, string)).map((maybeValue) => { - defaultValue = defaultValue || moment.invalid(); - if (isNothing(maybeValue)) return defaultValue; - const value = maybeValue.getOrElseValue(null!); - return !value.length ? defaultValue : moment(value, mode); - }); -} diff --git a/src/searchInString.ts b/src/searchInString.ts deleted file mode 100644 index 79a1ff2..0000000 --- a/src/searchInString.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function searchInString(inString: string, target: string, min = 0): boolean { - return target.length > min ? inString.trim().toLowerCase().indexOf(target.trim().toLowerCase()) !== -1 : true; -}