forked from ddrilling/asb_cloud_front
Документирована часть комментариев и функций
This commit is contained in:
parent
d235b01c80
commit
7af33d702d
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"день"
|
||||
"день",
|
||||
"Saub"
|
||||
],
|
||||
"liveServer.settings.port": 5501
|
||||
}
|
@ -5,6 +5,11 @@ import { DatePickerWrapper, getObjectByDeepKey } from '..'
|
||||
import { DatePickerWrapperProps } from '../DatePickerWrapper'
|
||||
import { formatDate, isRawDate } from '@utils'
|
||||
|
||||
/**
|
||||
* Фабрика методов сортировки столбцов для данных типа **Дата**
|
||||
* @param key Ключ столбца
|
||||
* @returns Метод сортировки
|
||||
*/
|
||||
export const makeDateSorter = <T extends unknown>(key: Key): SorterMethod<T> => (a, b) => {
|
||||
const vA = a ? getObjectByDeepKey(key, a) : null
|
||||
const vB = b ? getObjectByDeepKey(key, b) : null
|
||||
@ -16,6 +21,17 @@ export const makeDateSorter = <T extends unknown>(key: Key): SorterMethod<T> =>
|
||||
return (new Date(vA)).getTime() - (new Date(vB)).getTime()
|
||||
}
|
||||
|
||||
/**
|
||||
* Фабрика объектов-столбцов для компонента `Table` для работы с данными типа **Дата**
|
||||
*
|
||||
* @param title Название столбца
|
||||
* @param key Ключ столбца
|
||||
* @param utc Конвертировать ли дату в UTC
|
||||
* @param format Формат отображения даты
|
||||
* @param other Дополнительные опции столбца
|
||||
* @param pickerOther Опции компонента селектора даты
|
||||
* @returns Объект-столбец для работы с данными типа **Дата**
|
||||
*/
|
||||
export const makeDateColumn = <T extends unknown>(
|
||||
title: ReactNode,
|
||||
key: string,
|
||||
|
@ -6,8 +6,11 @@ import moment, { Moment } from 'moment'
|
||||
import { defaultFormat } from '@utils'
|
||||
|
||||
export type DatePickerWrapperProps = PickerDateProps<Moment> & {
|
||||
/** Значение селектора */
|
||||
value?: Moment,
|
||||
/** Метод вызывается при изменений даты */
|
||||
onChange?: (date: Moment | null) => any
|
||||
/** Конвертировать ли значение в UTC */
|
||||
isUTC?: boolean
|
||||
}
|
||||
|
||||
|
@ -9,31 +9,41 @@ import { defaultFormat } from '@utils'
|
||||
const { RangePicker } = DatePicker
|
||||
|
||||
export type DateRangeWrapperProps = RangePickerSharedProps<Moment> & {
|
||||
value?: RangeValue<Moment>,
|
||||
/** Значение селектора в виде массива из 2 элементов (от, до) */
|
||||
value?: RangeValue<Moment>
|
||||
/** Конвертировать ли значения в UTC */
|
||||
isUTC?: boolean
|
||||
/** Разрешить сброс значения селектора */
|
||||
allowClear?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготавливает значения к передаче в селектор
|
||||
*
|
||||
* @param value Массиз из 2 дат
|
||||
* @param isUTC Конвертировать ли значения в UTC
|
||||
* @returns Подготовленные даты
|
||||
*/
|
||||
const normalizeDates = (value?: RangeValue<Moment>, isUTC?: boolean): RangeValue<Moment> => {
|
||||
if (!value) return [null, null]
|
||||
return [
|
||||
value[0] ? (isUTC ? moment.utc(value[0]).local() : moment(value[0])) : null,
|
||||
value[1] ? (isUTC ? moment.utc(value[1]).local() : moment(value[1])) : null,
|
||||
]
|
||||
if (!value) return [null, null]
|
||||
return [
|
||||
value[0] ? (isUTC ? moment.utc(value[0]).local() : moment(value[0])) : null,
|
||||
value[1] ? (isUTC ? moment.utc(value[1]).local() : moment(value[1])) : null,
|
||||
]
|
||||
}
|
||||
|
||||
export const DateRangeWrapper = memo<DateRangeWrapperProps>(({ value, isUTC, allowClear = false, ...other }) => (
|
||||
<RangePicker
|
||||
showTime
|
||||
allowClear={allowClear}
|
||||
format={defaultFormat}
|
||||
defaultValue={[
|
||||
moment().subtract(1, 'days').startOf('day'),
|
||||
moment().startOf('day'),
|
||||
]}
|
||||
value={normalizeDates(value)}
|
||||
{...other}
|
||||
/>
|
||||
export const DateRangeWrapper = memo<DateRangeWrapperProps>(({ value, isUTC, allowClear, ...other }) => (
|
||||
<RangePicker
|
||||
showTime
|
||||
allowClear={allowClear}
|
||||
format={defaultFormat}
|
||||
defaultValue={[
|
||||
moment().subtract(1, 'days').startOf('day'),
|
||||
moment().startOf('day'),
|
||||
]}
|
||||
value={normalizeDates(value, isUTC)}
|
||||
{...other}
|
||||
/>
|
||||
))
|
||||
|
||||
export default DateRangeWrapper
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Key, memo, useCallback, useEffect, useState } from 'react'
|
||||
import { ColumnGroupType, ColumnType } from 'antd/lib/table'
|
||||
import { Table as RawTable, TableProps } from 'antd'
|
||||
import { Table as RawTable, TableProps as RawTableProps } from 'antd'
|
||||
|
||||
import { RenderMethod } from './Columns'
|
||||
import { tryAddKeys } from './EditableTable'
|
||||
@ -14,16 +14,28 @@ export type BaseTableColumn<T> = ColumnGroupType<T> | ColumnType<T>
|
||||
export type TableColumn<T> = OmitExtends<BaseTableColumn<T>, TableColumnSettings>
|
||||
export type TableColumns<T> = TableColumn<T>[]
|
||||
|
||||
export type TableContainer<T> = TableProps<T> & {
|
||||
export type TableProps<T> = RawTableProps<T> & {
|
||||
/** Массив колонок таблицы с настройками (описаны в `TableColumnSettings`) */
|
||||
columns: TableColumn<T>[]
|
||||
/** Название таблицы для сохранения настроек */
|
||||
tableName?: string
|
||||
/** Отображать ли кнопку настроек */
|
||||
showSettingsChanger?: boolean
|
||||
}
|
||||
|
||||
export interface DataSet<T, D = any> {
|
||||
[k: Key]: DataSet<T> | T | D
|
||||
[k: Key]: DataSet<T, D> | T | D
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить значение из объекта по составному ключу
|
||||
*
|
||||
* Составной ключ имеет вид: `<поле 1>[.<поле 2>...]`
|
||||
*
|
||||
* @param key Составной ключ
|
||||
* @param data Объект из которого будет полученно значение
|
||||
* @returns Значение, найденное по ключу, либо `undefined`
|
||||
*/
|
||||
export const getObjectByDeepKey = <T,>(key: Key | undefined, data: DataSet<T>): T | undefined => {
|
||||
if (!key) return undefined
|
||||
const parts = String(key).split('.')
|
||||
@ -36,36 +48,44 @@ export const getObjectByDeepKey = <T,>(key: Key | undefined, data: DataSet<T>):
|
||||
return out as T
|
||||
}
|
||||
|
||||
/**
|
||||
* Фабрика обёрток render-функций ячеек с поддержкой составных ключей
|
||||
* @param key Составной ключ
|
||||
* @param render Стандартная render-функция
|
||||
* @returns Обёрнутая render-функция
|
||||
*/
|
||||
export const makeColumnRenderWrapper = <T extends DataSet<any>>(key: Key | undefined, render: RenderMethod<T, T> | undefined): RenderMethod<T, T> =>
|
||||
(_: any, dataset: T, index: number) => {
|
||||
const renderFunc: RenderMethod<T, T> = typeof render === 'function' ? render : (record) => String(record)
|
||||
return renderFunc(getObjectByDeepKey<T>(key, dataset), dataset, index)
|
||||
}
|
||||
|
||||
|
||||
const applyColumnWrappers = <T extends DataSet<any>>(columns: BaseTableColumn<T>[]): BaseTableColumn<T>[] => {
|
||||
return columns.map((column) => {
|
||||
if ('children' in column) {
|
||||
return {
|
||||
...column,
|
||||
children: applyColumnWrappers(column.children),
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Применяет необходимые обёртки ко всем столбцам таблицы
|
||||
* @param columns Исходные столбцы
|
||||
* @returns Обёрнутые столбцы
|
||||
*/
|
||||
const applyColumnWrappers = <T extends DataSet<any>>(columns: TableColumns<T>): TableColumns<T> => columns.map((column) => {
|
||||
if ('children' in column) {
|
||||
return {
|
||||
...column,
|
||||
render: makeColumnRenderWrapper<T>(column.key, column.render),
|
||||
children: applyColumnWrappers(column.children),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return {
|
||||
...column,
|
||||
render: makeColumnRenderWrapper<T>(column.key, column.render),
|
||||
}
|
||||
})
|
||||
|
||||
function _Table<T extends DataSet<any>>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer<T>) {
|
||||
function _Table<T extends DataSet<any>>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableProps<T>) {
|
||||
const [newColumns, setNewColumns] = useState<TableColumn<T>[]>([])
|
||||
const [settings, setSettings] = useState<TableSettings>({})
|
||||
|
||||
const onSettingsChanged = useCallback((settings?: TableSettings | null) => {
|
||||
if (tableName)
|
||||
setTableSettings(tableName, settings)
|
||||
setSettings(settings ?? {})
|
||||
setSettings(settings || {})
|
||||
}, [tableName])
|
||||
|
||||
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
|
||||
@ -92,6 +112,13 @@ function _Table<T extends DataSet<any>>({ columns, dataSource, tableName, showSe
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Обёртка над компонентом таблицы AntD
|
||||
*
|
||||
* Особенности:
|
||||
* * Поддержка составных ключей столбцов
|
||||
* * Работа с настройками столбцов таблицы
|
||||
*/
|
||||
export const Table = memo(_Table) as typeof _Table
|
||||
|
||||
export default Table
|
||||
|
@ -6,8 +6,11 @@ import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils'
|
||||
import { TimeDto } from '@api'
|
||||
|
||||
export type TimePickerWrapperProps = Omit<Omit<TimePickerProps, 'value'>, 'onChange'> & {
|
||||
/** Текущее значение */
|
||||
value?: TimeDto,
|
||||
/** Метод вызывается при изменений времени */
|
||||
onChange?: (date: TimeDto | null) => any
|
||||
/** Конвертировать ли время в UTC */
|
||||
isUTC?: boolean
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,13 @@ export type PaginationContainer<T> = {
|
||||
items?: T[] | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует объект пагинации для компонента `Table` из данных от сервисов
|
||||
*
|
||||
* @param сontainer данные от сервиса
|
||||
* @param other Дополнительные поля (передаются в объект напрямую в приоритете)
|
||||
* @returns Объект пагинации
|
||||
*/
|
||||
export const makePaginationObject = <T, M extends object>(сontainer: PaginationContainer<T>, other: M) => ({
|
||||
...other,
|
||||
pageSize: сontainer.take,
|
||||
|
@ -9,6 +9,7 @@ export type CompanyViewProps = {
|
||||
company?: CompanyDto
|
||||
}
|
||||
|
||||
/** Компонент для отображения информации о компании */
|
||||
export const CompanyView = memo<CompanyViewProps>(({ company }) => company ? (
|
||||
<Tooltip title={
|
||||
<Grid style={{ columnGap: '8px' }}>
|
||||
|
@ -8,6 +8,7 @@ export type PermissionViewProps = {
|
||||
info?: PermissionDto
|
||||
}
|
||||
|
||||
/** Компонент для отображения информации о разрешении */
|
||||
export const PermissionView = memo<PermissionViewProps>(({ info }) => info ? (
|
||||
<Tooltip overlayInnerStyle={{ width: '400px' }} title={
|
||||
<Grid>
|
||||
|
@ -9,6 +9,7 @@ export type RoleViewProps = {
|
||||
role?: UserRoleDto
|
||||
}
|
||||
|
||||
/** Компонент для отображения информации о роли */
|
||||
export const RoleView = memo<RoleViewProps>(({ role }) => {
|
||||
if (!role) return ( <Tooltip title={'нет данных'}>-</Tooltip> )
|
||||
|
||||
|
@ -19,6 +19,12 @@ export const lables: Record<string, string> = {
|
||||
spinPlcVersion: 'Версия Спин Мастер',
|
||||
}
|
||||
|
||||
/**
|
||||
* Строит название для телеметрии
|
||||
*
|
||||
* @param telemetry Объект телеметрии
|
||||
* @returns Название
|
||||
*/
|
||||
export const getTelemetryLabel = (telemetry?: TelemetryDto) =>
|
||||
`${telemetry?.id ?? '-'} / ${telemetry?.info?.deposit ?? '-'} / ${telemetry?.info?.cluster ?? '-'} / ${telemetry?.info?.well ?? '-'}`
|
||||
|
||||
@ -26,6 +32,7 @@ export type TelemetryViewProps = {
|
||||
telemetry?: TelemetryDto
|
||||
}
|
||||
|
||||
/** Компонент для отображения информации о телеметрии */
|
||||
export const TelemetryView = memo<TelemetryViewProps>(({ telemetry }) => telemetry?.info ? (
|
||||
<Tooltip
|
||||
overlayInnerStyle={{ width: '400px' }}
|
||||
|
@ -10,6 +10,7 @@ export type UserViewProps = HTMLProps<HTMLSpanElement> & {
|
||||
user?: UserDto
|
||||
}
|
||||
|
||||
/** Компонент для отображения информации о пользователе */
|
||||
export const UserView = memo<UserViewProps>(({ user, ...other }) =>
|
||||
user ? (
|
||||
<Tooltip
|
||||
|
@ -19,8 +19,14 @@ export type WellViewProps = TooltipProps & {
|
||||
labelProps?: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить название скважины
|
||||
* @param well Объект с данными скважины
|
||||
* @returns Название скважины
|
||||
*/
|
||||
export const getWellTitle = (well: WellDto) => `${well.deposit || '-'} / ${well.cluster || '-'} / ${well.caption || '-'}`
|
||||
|
||||
/** Компонент для отображения информации о скважине */
|
||||
export const WellView = memo<WellViewProps>(({ well, iconProps, labelProps, ...other }) => well ? (
|
||||
<Tooltip {...other} title={(
|
||||
<Grid style={{ columnGap: '8px' }}>
|
||||
|
@ -21,6 +21,7 @@ export type WirelineViewProps = TooltipProps & {
|
||||
buttonProps?: ButtonProps
|
||||
}
|
||||
|
||||
/** Компонент для отображения информации о талевом канате */
|
||||
export const WirelineView = memo<WirelineViewProps>(({ wireline, buttonProps, ...other }) => (
|
||||
<Tooltip
|
||||
{...other}
|
||||
|
@ -7,7 +7,7 @@ import LoaderPortal from '@components/LoaderPortal'
|
||||
import { DateRangeWrapper } from '@components/Table'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { unique } from '@utils/filters'
|
||||
import { getPermissions, arrayOrDefault, range, withPermissions, pretify } from '@utils'
|
||||
import { getPermissions, arrayOrDefault, range, withPermissions, prettify } from '@utils'
|
||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||
|
||||
import DrillerList from './DrillerList'
|
||||
@ -67,7 +67,7 @@ const Operations = memo(() => {
|
||||
const maxTarget = Math.max(...data.operations?.map((op) => op.operationValue?.targetValue || 0))
|
||||
const uniqueOps = data.operations?.map((op) => op.value || 0).filter(unique)
|
||||
const value = uniqueOps.reduce((out, op) => out + op, 0) / uniqueOps.length * 3 / 2
|
||||
setYDomain(pretify(Math.max(maxTarget, value)))
|
||||
setYDomain(prettify(Math.max(maxTarget, value)))
|
||||
}, [data])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -8,7 +8,7 @@ import { useTopRightBlock, useWell } from '@asb/context'
|
||||
import { D3Chart } from '@components/d3'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { formatDate, fractionalSum, withPermissions, getOperations, pretify } from '@utils'
|
||||
import { formatDate, fractionalSum, withPermissions, getOperations, prettify } from '@utils'
|
||||
|
||||
import TLPie from './TLPie'
|
||||
import TLChart from './TLChart'
|
||||
@ -180,7 +180,7 @@ const Tvd = memo(({ well: givenWell, title, ...other }) => {
|
||||
.map(([_, ops]) => Math.max(...ops.map((op) => op.depth).filter(Boolean)))
|
||||
.filter(Boolean)
|
||||
)
|
||||
const minValue = pretify(maxValue)
|
||||
const minValue = prettify(maxValue)
|
||||
|
||||
return {
|
||||
date: {
|
||||
|
@ -1,12 +1,30 @@
|
||||
import { getObjectByDeepKey } from "@asb/components/Table"
|
||||
|
||||
/**
|
||||
* Фабрика методов фильтрации строк таблицы по столбцу с текстом
|
||||
* @param key Составной ключ столбца
|
||||
* @returns Метод фильтрации
|
||||
*/
|
||||
export const makeTextOnFilter = (key: string) =>
|
||||
(value: string, record?: Record<string, unknown>) => String(record?.[key]).startsWith(value)
|
||||
(value: string, record?: Record<string, unknown>) => record && String(getObjectByDeepKey(key, record)).startsWith(value)
|
||||
|
||||
/**
|
||||
* Фабрика методов фильтрации строк таблицы по столбцу с массивами
|
||||
* @param key Составной ключ столбца
|
||||
* @returns Метод фильтрации
|
||||
*/
|
||||
export const makeArrayOnFilter = (key: string) =>
|
||||
(value: string, record?: Record<string, string[]>) => (!value && (record?.[key]?.length ?? 0) <= 0) || record?.[key]?.includes(value)
|
||||
|
||||
export const makeObjectOnFilter = (field: string, key: string) =>
|
||||
(value: string, record?: Record<string, Record<string, unknown>>) => String(record?.[field]?.[key]).startsWith(value)
|
||||
(value: string, record?: Record<string, string[]>) => record && (
|
||||
(!value && (getObjectByDeepKey<any[]>(key, record)?.length ?? 0) <= 0) ||
|
||||
getObjectByDeepKey<any[]>(key, record)?.includes(value)
|
||||
)
|
||||
|
||||
/**
|
||||
* Создаёт список значений для фильтрации текстовых столбцов
|
||||
* @param array Массив значений
|
||||
* @param keys Массив ключей
|
||||
* @returns Список значений
|
||||
*/
|
||||
export const makeTextFilters = (array: Record<string, unknown>[], keys: string[]) => {
|
||||
const filters: string[][] = Array(keys.length)
|
||||
|
||||
|
@ -1,3 +1,11 @@
|
||||
export * from './columnFilters'
|
||||
|
||||
/**
|
||||
* Проверяет значение на уникальность в массиве
|
||||
*
|
||||
* @param value Проверяемое значение
|
||||
* @param index Индекс проверяемого значения в массиве
|
||||
* @param self Массив, содержащий значение
|
||||
* @returns Является ли значение уникальным или первым
|
||||
*/
|
||||
export const unique = <T,>(value: T, index: number, self: T[]) => self.indexOf(value) === index
|
||||
|
@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Возвращает
|
||||
*
|
||||
* Гарантированно возвращает массив нужного типа
|
||||
*
|
||||
* @param arr Входящие данные
|
||||
* @param def Значение по-умолчанию
|
||||
*
|
||||
* @returns Если `arr` - массив будет возвращено оно, иначе `def`
|
||||
*
|
||||
* @returns Если входящие данные - массив будут возвращены они, иначе значение из `def`
|
||||
*/
|
||||
export const arrayOrDefault = <T,>(arr: unknown, def: T[] = []): T[] => arr instanceof Array ? arr : def
|
||||
|
||||
|
@ -3,6 +3,12 @@ import { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon'
|
||||
|
||||
import { BaseDataType, ChartDataset } from '@components/d3'
|
||||
|
||||
/**
|
||||
* Фабрика методов-оптимизаторов для массивов точек перед выводом на график
|
||||
*
|
||||
* @param isEquals Метод сравнения элементов на равенство
|
||||
* @returns Метод вырезки идентичных элементов (по результатам работы переданного метода)
|
||||
*/
|
||||
export const makePointsOptimizator = <DataType extends BaseDataType>(isEquals: (a: DataType, b: DataType) => boolean) => (points: DataType[]) => {
|
||||
if (!Array.isArray(points) || points.length < 3) return points
|
||||
|
||||
@ -16,6 +22,22 @@ export const makePointsOptimizator = <DataType extends BaseDataType>(isEquals: (
|
||||
|
||||
export type TouchType = 'all' | 'x' | 'y'
|
||||
|
||||
/**
|
||||
* Получает расстояние между точками в зависимости от типа касания
|
||||
*
|
||||
* Если тип касания 'x', то вернёт модуль разницы абсцисс
|
||||
*
|
||||
* Если тип касания 'y', то вернёт модуль разницы ординат
|
||||
*
|
||||
* Иначе вернёт корень суммы квадратов разностей координат точек
|
||||
*
|
||||
* @param x1 Абсцисса первой точки
|
||||
* @param y1 Ордината первой точки
|
||||
* @param x2 Абсцисса второй точки
|
||||
* @param y2 Ордината второй точки
|
||||
* @param type Тип касания
|
||||
* @returns Расстояние между точками
|
||||
*/
|
||||
export const getDistance = (x1: number, y1: number, x2: number, y2: number, type: TouchType = 'all') => {
|
||||
if (type === 'x') return Math.abs(x1 - x2)
|
||||
if (type === 'y') return Math.abs(y1 - y2)
|
||||
@ -23,6 +45,13 @@ export const getDistance = (x1: number, y1: number, x2: number, y2: number, type
|
||||
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает иконку графика в зависимости от типа
|
||||
*
|
||||
* @param chart График, у которого будет проверен тип
|
||||
* @param options Дополнительные опции иконки
|
||||
* @returns Элемент иконки
|
||||
*/
|
||||
export const getChartIcon = <DataType extends BaseDataType>(chart: ChartDataset<DataType>, options?: Omit<AntdIconProps, 'ref'>) => {
|
||||
let Icon
|
||||
switch (chart.type) {
|
||||
|
@ -16,10 +16,22 @@ export enum timeInS {
|
||||
week = day * 7,
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка значения на возможность преобразования к дате
|
||||
*
|
||||
* @param value Проверяемое значение
|
||||
* @returns Является ли значение потенциальной датой
|
||||
*/
|
||||
export function isRawDate(value: unknown): value is RawDate {
|
||||
return !isNaN(Date.parse(String(value)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка значения на возможность преобразования к `TimeDto`
|
||||
*
|
||||
* @param value Проверяемое значение
|
||||
* @returns Является ли значение потенциальным объектом `TimeDto`
|
||||
*/
|
||||
export function isTime(value: unknown): value is TimeDto {
|
||||
if (!value || typeof value !== 'object')
|
||||
return false
|
||||
@ -27,18 +39,40 @@ export function isTime(value: unknown): value is TimeDto {
|
||||
return ['hour', 'minute', 'second'].every((key) => keys.includes(key))
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматировать значение как дату в строку
|
||||
*
|
||||
* @param date Форматируемое значение
|
||||
* @param utc Преобразовывать ли к UTC
|
||||
* @param format Формат вывода
|
||||
* @returns Форматированная дата в строке
|
||||
*/
|
||||
export const formatDate = (date: unknown, utc: boolean = false, format: string = defaultFormat) => {
|
||||
if (!isRawDate(date)) return null
|
||||
const out = utc ? moment.utc(date).local() : moment(date)
|
||||
return out.format(format)
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматировать значение как время в строку
|
||||
*
|
||||
* @param time Форматируемое значение
|
||||
* @param utc Преобразовывать ли к UTC
|
||||
* @param format Формат вывода
|
||||
* @returns Форматированное время в строке
|
||||
*/
|
||||
export const formatTime = (time: unknown, utc: boolean = false, format: string = defaultTimeFormat) => {
|
||||
if(!isTime(time)) return
|
||||
const out = timeToMoment(time, utc, format)
|
||||
return out.format(format)
|
||||
}
|
||||
|
||||
/**
|
||||
* Привести секунды к строке периоду
|
||||
*
|
||||
* @param time Указанное кол-во секунд
|
||||
* @returns Форматированная строка период
|
||||
*/
|
||||
export const periodToString = (time?: number) => {
|
||||
if (!time || time <= 0) return '00:00:00'
|
||||
const days = Math.floor(time / timeInS.day)
|
||||
@ -54,11 +88,26 @@ export const periodToString = (time?: number) => {
|
||||
return `${days > 0 ? days : ''} ${toFixed(hours)}:${toFixed(minutes)}:${toFixed(seconds)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычислить кол-во дней между двумя датами
|
||||
*
|
||||
* @param start Левая дата
|
||||
* @param end Правая дата
|
||||
* @returns если оба аргумента потенциальны даты, то кол-во дней между ними, иначе `undefined`
|
||||
*/
|
||||
export const calcDuration = (start: unknown, end: unknown): number | undefined => {
|
||||
if (!isRawDate(start) || !isRawDate(end)) return undefined
|
||||
return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day
|
||||
}
|
||||
|
||||
/**
|
||||
* Сдвинуть дату на указанное время
|
||||
*
|
||||
* @param date Исходная дата
|
||||
* @param value Коэффициент сдвига
|
||||
* @param type Тип сдвига (день, час, минут и т.д.)
|
||||
* @returns Смещённая дата
|
||||
*/
|
||||
export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate | null => {
|
||||
if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return null
|
||||
const d = new Date(date)
|
||||
@ -86,17 +135,41 @@ export const rawTimezones = Object.freeze({
|
||||
|
||||
export type TimezoneId = keyof typeof rawTimezones
|
||||
|
||||
/**
|
||||
* Проверяет, является ли переданное значение корректным ID часовой зоны
|
||||
*
|
||||
* @param value Проверяемое значение
|
||||
* @returns Является ли переданное значение корректным ID часовой зоны
|
||||
*/
|
||||
export const isTimezoneId = (value: unknown): value is TimezoneId => !!value && String(value) in rawTimezones
|
||||
|
||||
/**
|
||||
* Ищет часовую зону для переданной телеметрии
|
||||
* @param value Данные телеметрии
|
||||
* @returns Название часовой зоны
|
||||
*/
|
||||
export const findTimezoneId = (value: SimpleTimezoneDto): TimezoneId =>
|
||||
(isTimezoneId(value.timezoneId) && value.timezoneId) ||
|
||||
(Object.keys(rawTimezones) as TimezoneId[]).find(id => rawTimezones[id] === value.hours) as TimezoneId
|
||||
|
||||
/**
|
||||
* Приводит `TimeDto`-объект к `Moment`-объекту
|
||||
*
|
||||
* @param time `TimeDto`-объект
|
||||
* @param isUtc Приводить ли к UTC
|
||||
* @param format Формат для обработки
|
||||
* @returns `Moment`-объект
|
||||
*/
|
||||
export const timeToMoment = (time?: TimeDto | null, isUtc?: boolean, format: string = defaultTimeFormat): Moment => {
|
||||
const input = `${time?.hour ?? 0}:${time?.minute ?? 0}:${time?.second ?? 0}`
|
||||
return isUtc ? moment.utc(input, format).local() : moment(input, format)
|
||||
}
|
||||
|
||||
/**
|
||||
* Приводит `Moment`-объект к `TimeDto`-объекту
|
||||
* @param time `Moment`-объект
|
||||
* @returns `TimeDto`-объекту
|
||||
*/
|
||||
export const momentToTime = (time?: Moment | null): TimeDto => ({
|
||||
hour: time?.hour() ?? 0,
|
||||
minute: time?.minute() ?? 0,
|
||||
|
@ -1,10 +1,18 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
/**
|
||||
* Форматирует число по заданным параметрам
|
||||
*
|
||||
* @param number Форматируемое число
|
||||
* @param def Вывод по-умолчанию
|
||||
* @param fixed Длина числа после форматирования
|
||||
* @returns Строка - форматированное число
|
||||
*/
|
||||
export const getPrecision = (number: number, def: string = '-', fixed: number = 2): string => Number.isFinite(number) ? number.toFixed(fixed) : def
|
||||
|
||||
/**
|
||||
* Генерирует функцию ограничения значения в заданном диапазоне
|
||||
*
|
||||
*
|
||||
* @param min Минимальное значение
|
||||
* @param max Максимальное значение
|
||||
* @returns Функция, ограничивающая значение в диапазоне [`min`; `max`]
|
||||
@ -17,15 +25,20 @@ export const limitValue = <T,>(min: T, max: T) => (value: T) => {
|
||||
|
||||
/**
|
||||
* Генерирует массив чисел в заданном диапазоне
|
||||
*
|
||||
*
|
||||
* @param end Конечное значение
|
||||
* @param start Начальное значение
|
||||
*
|
||||
*
|
||||
* @returns Массив чисел в диапазоне от `start` до `end`
|
||||
*/
|
||||
export const range = (end: number, start: number = 0) => Array(end - start).fill(undefined).map((_, i) => start + i)
|
||||
|
||||
export const pretify = (n: number): number | null => {
|
||||
/**
|
||||
* Округляет число до ближайшего красивого
|
||||
* @param n Округляемое число
|
||||
* @returns Округлённое число
|
||||
*/
|
||||
export const prettify = (n: number): number | null => {
|
||||
if (!Number.isFinite(n)) return null
|
||||
let i = 0
|
||||
for (; Math.abs(n) >= 100; i++) n /= 10
|
||||
|
@ -1,21 +1,21 @@
|
||||
/**
|
||||
* Копирует данные в глубину.
|
||||
*
|
||||
* Копирует данные с максимальной глубиной
|
||||
*
|
||||
* @remarks
|
||||
* При копированиий объектов, содержащих функций может возникнуть исключение.
|
||||
* При копирований объектов, содержащих функций может возникнуть исключение.
|
||||
* Не предназначено для копирования функций.
|
||||
*
|
||||
*
|
||||
* @param data Копируемые данные
|
||||
* @returns Полная копия `data`
|
||||
* @returns Полная копия исходных данных
|
||||
*/
|
||||
export const deepCopy = <T,>(data: T): T => JSON.parse(JSON.stringify(data ?? null))
|
||||
|
||||
/**
|
||||
* Маппинг полей объекта
|
||||
*
|
||||
* @param data Входящие данные
|
||||
* @param handler Обработчик
|
||||
*
|
||||
* Аналог функции `Array.prototype.map`, но для работы с объектами
|
||||
*
|
||||
* @param data Исходный объект
|
||||
* @param handler Метод-обработчик
|
||||
*
|
||||
* @returns Объект с обработанными полями
|
||||
*/
|
||||
export const wrapValues = <T, R>(data: Record<string, T>, handler: (data: T, key: string, object: Record<string, T>) => R): Record<string, R> =>
|
||||
|
@ -12,10 +12,22 @@ export type ServiceName = string
|
||||
export type ServiceRequestType = 'get' | 'edit' | 'delete'
|
||||
export type PermissionRequest = `${ServiceName}.${ServiceRequestType}`
|
||||
|
||||
/**
|
||||
* Проверка соответствует ли значение типу `ServiceRequestType`
|
||||
*
|
||||
* @param value Проверяемое значение
|
||||
* @returns Является ли значение объектом типа `ServiceRequestType`
|
||||
*/
|
||||
export function isRequestType(value: string): value is ServiceRequestType {
|
||||
return ['get', 'edit', 'delete'].includes(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация объекта, содержащего информацию о наличии или отсутствия перечисленных разрешений
|
||||
*
|
||||
* @param values Список разрешений
|
||||
* @returns Объект с информацией о разрешениях
|
||||
*/
|
||||
export const getPermissions = (...values: PermissionRequest[]) => {
|
||||
const permissions: Record<string, Partial<Record<ServiceRequestType, boolean>>> = {}
|
||||
values.forEach((key) => {
|
||||
@ -27,6 +39,13 @@ export const getPermissions = (...values: PermissionRequest[]) => {
|
||||
return permissions
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка наличия у пользователя разрешения или списка разрешений
|
||||
*
|
||||
* @param permission Разрешение или список разрешений
|
||||
* @param userPermissions Список разрешений пользователя (если не указано, будут получены из локального хранилища)
|
||||
* @returns `true` если все разрешения присутствуют, иначе `false`
|
||||
*/
|
||||
export const hasPermission = (permission?: Permission | Permission[], userPermissions?: Permission[]): boolean => {
|
||||
if (!Array.isArray(permission) && typeof permission !== 'string')
|
||||
return true
|
||||
@ -36,6 +55,13 @@ export const hasPermission = (permission?: Permission | Permission[], userPermis
|
||||
return permission.every((perm) => userPerms.includes(perm))
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка доступности секции сайта для посещения пользователем
|
||||
*
|
||||
* @param section Секция сайта
|
||||
* @param userPermission Разрешения пользователя
|
||||
* @returns `true` если все разрешения присутствуют, иначе `false`
|
||||
*/
|
||||
const sectionAvailable = (section: PermissionRecord, userPermission: Permission[]) => {
|
||||
for (const child of Object.values(section)) {
|
||||
if (!child) continue
|
||||
@ -48,6 +74,12 @@ const sectionAvailable = (section: PermissionRecord, userPermission: Permission[
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка доступности URL для посещения пользователем
|
||||
* @param path URL
|
||||
* @param userPermissions Разрешения пользователя (если не заданы, будут получены из локального хранилища)
|
||||
* @returns `true` если все разрешения присутствуют, иначе `false`
|
||||
*/
|
||||
export const isURLAvailable = (path: string, userPermissions?: Permission[]) => {
|
||||
if (publicPages.includes(path)) return true
|
||||
|
||||
@ -95,14 +127,26 @@ export const NoAccessComponent = memo(() => getUser().login ? (
|
||||
<Navigate to={'/login'} replace />
|
||||
))
|
||||
|
||||
/**
|
||||
* HOC добавляющий проверку на наличие разрешений для отображения компонента
|
||||
* @param Component Исходных компонент
|
||||
* @param requirements Необходимые разрешения
|
||||
* @param elseNode Компонент по-умолчанию
|
||||
* @returns Обёрнутый компонент с проверкой разрешений
|
||||
*/
|
||||
export const withPermissions = <P extends object>(
|
||||
Component: NamedExoticComponent<P> | ((props: P) => ReactElement),
|
||||
requirements: Permission[] = [],
|
||||
elseNode: JSX.Element = <NoAccessComponent />
|
||||
): PrivateComponent<P> => Object.assign(memo<P>(function PrivateWrapper(props) {
|
||||
): PrivateComponent<P> => memo<P>(function PrivateWrapper(props) {
|
||||
return hasPermission(requirements) ? <Component {...props} /> : elseNode
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* Получить url текущей выбранной вкладки
|
||||
*
|
||||
* @returns url текущей выбранной вкладки
|
||||
*/
|
||||
export const getTabname = () => {
|
||||
const params = useParams()
|
||||
const attr = useMemo(() => params['*']?.split('/').filter(s => s)[0] ?? null, [params])
|
||||
|
@ -11,10 +11,15 @@ export type SaubData = WellOperationDto & {
|
||||
depth?: number
|
||||
/** Дата */
|
||||
date?: string
|
||||
/** Колличество часов НПВ с начала бурения до текущего момента */
|
||||
/** Количество часов НПВ с начала бурения до текущего момента */
|
||||
nptHours?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить списки операций для конкретной скважины
|
||||
* @param idWell ID скважины
|
||||
* @returns Списки операций
|
||||
*/
|
||||
export const getOperations = async (idWell: number): Promise<{
|
||||
operations: WellOperationDtoPlanFactPredictBase[],
|
||||
plan: SaubData[]
|
||||
|
@ -16,12 +16,25 @@ export enum StorageNames {
|
||||
witsInfo = 'witsInfo'
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить массив значений из локального хранилища
|
||||
*
|
||||
* @param name Имя массива
|
||||
* @param sep Разделитель элементов
|
||||
* @returns Массив значений или `null`
|
||||
*/
|
||||
export const getArrayFromLocalStorage = <T extends string = string>(name: string, sep: string | RegExp = ','): T[] | null => {
|
||||
const raw = localStorage.getItem(name)
|
||||
if (!raw) return null
|
||||
return raw.split(sep).map<T>(elm => elm as T)
|
||||
return raw.split(sep) as T[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить объект из JSON строки в локальном хранилище
|
||||
*
|
||||
* @param name Имя строки
|
||||
* @returns Прочитанный объект или `null`
|
||||
*/
|
||||
export const getJSON = <T,>(name: StorageNames): T | null => {
|
||||
try {
|
||||
const raw = localStorage.getItem(name)
|
||||
@ -32,6 +45,13 @@ export const getJSON = <T,>(name: StorageNames): T | null => {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Записать объект в локальное хранилище, как JSON строка
|
||||
*
|
||||
* @param name Имя строки
|
||||
* @param data Сохраняемый объект
|
||||
* @returns `true` если сохранение успешно, иначе `false`
|
||||
*/
|
||||
export const setJSON = <T,>(name: StorageNames, data: T | null): boolean => {
|
||||
try {
|
||||
localStorage.setItem(name, JSON.stringify(data))
|
||||
@ -42,8 +62,17 @@ export const setJSON = <T,>(name: StorageNames, data: T | null): boolean => {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить информацию о пользователе из локального хранилища
|
||||
*
|
||||
* @returns Объект данных о пользователе
|
||||
*/
|
||||
export const getUser = (): UserTokenDto => getJSON(StorageNames.user) || {}
|
||||
|
||||
/**
|
||||
* Получить разрешения пользователя из локального хранилища
|
||||
* @returns Список разрешений или `null`
|
||||
*/
|
||||
export const getUserPermissions = (): Permission[] | null => {
|
||||
let permissions = getUser()?.permissions?.map((perm) => perm.name as string)
|
||||
if (!permissions) // TODO: Удалить в следующем релизе, вставлено для совместимости
|
||||
@ -51,11 +80,20 @@ export const getUserPermissions = (): Permission[] | null => {
|
||||
return permissions || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить данные пользователя в локальное хранилище
|
||||
*
|
||||
* @param user Данные пользователя
|
||||
* @returns `true` если сохранение успешно, иначе `false`
|
||||
*/
|
||||
export const setUser = (user: UserTokenDto) => {
|
||||
OpenAPI.TOKEN = user.token ?? undefined
|
||||
localStorage.setItem(StorageNames.user, JSON.stringify(user))
|
||||
return setJSON(StorageNames.user, user)
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистить данные о пользователе в локальном хранилище
|
||||
*/
|
||||
export const removeUser = () => {
|
||||
localStorage.removeItem(StorageNames.userId)
|
||||
localStorage.removeItem(StorageNames.login)
|
||||
@ -65,13 +103,26 @@ export const removeUser = () => {
|
||||
localStorage.removeItem(StorageNames.user)
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить объект настроек таблицы
|
||||
*
|
||||
* @param tableName Имя таблицы
|
||||
* @returns Объект настроек таблицы
|
||||
*/
|
||||
export const getTableSettings = (tableName: string): TableSettings => {
|
||||
const tables = getJSON<TableSettingsStore>(StorageNames.tableSettings) ?? {}
|
||||
if (!(tableName in tables)) return {}
|
||||
return wrapValues(tables[tableName] ?? {}, normalizeTableColumn)
|
||||
}
|
||||
|
||||
export const setTableSettings = (tableName: string, settings?: TableSettings | null): boolean => {
|
||||
/**
|
||||
* Сохранить объект настроек таблицы
|
||||
*
|
||||
* @param tableName Имя таблицы
|
||||
* @param settings Объект настроек
|
||||
* @returns `true` если сохранение успешно, иначе `false`
|
||||
*/
|
||||
export const setTableSettings = (tableName: string, settings?: TableSettings | null) => {
|
||||
const currentStore = getJSON<TableSettingsStore>(StorageNames.tableSettings) ?? {}
|
||||
currentStore[tableName] = wrapValues(settings ?? {}, optimizeTableColumn)
|
||||
return setJSON(StorageNames.tableSettings, currentStore)
|
||||
@ -81,4 +132,9 @@ export type DataDashboardNNB = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить настройки панели ННБ
|
||||
*
|
||||
* @returns Объект настроек панели ННБ
|
||||
*/
|
||||
export const getDashboardNNB = () => getJSON<DataDashboardNNB>(StorageNames.dashboardNNB)
|
||||
|
@ -1,10 +1,18 @@
|
||||
/**
|
||||
* Фабрика методов обрезки строк по словам с добавлением суффикса
|
||||
*
|
||||
* @param maxLength Максимальная длинна строки
|
||||
* @param separator Разделитель слов в строке
|
||||
* @param suffix Суффикс строки, отображаемый в случае обрезки
|
||||
* @returns Метод обрезки строки
|
||||
*/
|
||||
export const makeStringCutter = (maxLength: number = 100, separator: string = ' ', suffix: string = '...') => <T,>(comment: T): T | string => {
|
||||
if (!comment || typeof comment !== 'string' || comment.length <= maxLength)
|
||||
return comment
|
||||
if (maxLength <= suffix.length)
|
||||
return comment // Обрабатываются только строки с длинной выше максимальной
|
||||
if (maxLength <= suffix.length) // Если максимальная длина меньше длины суффикса вывести начало суффикса длинной `maxLength`
|
||||
return suffix.substring(0, maxLength)
|
||||
const lastSep = comment.lastIndexOf(separator, maxLength - suffix.length)
|
||||
if (lastSep < 0)
|
||||
const lastSep = comment.lastIndexOf(separator, maxLength - suffix.length) // Ищем последнее разделение слов перед максимальной длинной с вычетом длины суффикса
|
||||
if (lastSep < 0) // Если разделитель не найден обрезаем само слово и добавляем суффикс
|
||||
return comment.substring(0, maxLength - suffix.length) + suffix
|
||||
return comment.substring(0, lastSep) + suffix
|
||||
return comment.substring(0, lastSep) + suffix // Иначе обрезаем до разделителя и добавляем суффикс
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
|
||||
/**
|
||||
* Превращает SVG-элемент в `BLOB`
|
||||
* @param svg SVG-элемент
|
||||
* @returns `BLOB` строка
|
||||
*/
|
||||
export const svgToDataURL = (svg: SVGSVGElement) => {
|
||||
const serializer = new XMLSerializer()
|
||||
let source = serializer.serializeToString(svg)
|
||||
|
@ -9,20 +9,32 @@ export type TableColumnSettings = {
|
||||
export type TableSettings = Record<string, TableColumnSettings>
|
||||
export type TableSettingsStore = Record<string, TableSettings | null>
|
||||
|
||||
/**
|
||||
* Создаёт объект настроек таблицы исходя из массива столбцов
|
||||
* @param columns Массив столбцов таблицы
|
||||
* @returns Объект настроек таблицы
|
||||
*/
|
||||
export const makeTableSettings = <T extends object>(columns: TableColumns<T>): TableSettings => {
|
||||
const settings: TableSettings = {}
|
||||
columns.forEach((column) => {
|
||||
if (!column.key) return
|
||||
if (!column.key) return // Столбцы без ключей игнорируются
|
||||
const key = String(column.key)
|
||||
settings[key] = {
|
||||
columnName: key,
|
||||
title: typeof column.title === 'string' ? column.title : key,
|
||||
visible: column.visible ?? true,
|
||||
title: typeof column.title === 'string' ? column.title : key, // В качестве заголовка невозможно использовать `ReactNode`
|
||||
visible: column.visible ?? true, // По-умолчанию все столбцы видимые
|
||||
}
|
||||
})
|
||||
return settings
|
||||
}
|
||||
|
||||
/**
|
||||
* Объединяет несколько объектов настроек таблицы в один
|
||||
*
|
||||
* Приоритет объектом снижается с последнего влево
|
||||
* @param settings Список объектов настроек
|
||||
* @returns Совмещённый объект настроек
|
||||
*/
|
||||
export const mergeTableSettings = (...settings: TableSettings[]): TableSettings => {
|
||||
const newSettings: TableSettings = {}
|
||||
for (const setting of settings) {
|
||||
@ -36,17 +48,36 @@ export const mergeTableSettings = (...settings: TableSettings[]): TableSettings
|
||||
return newSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* Расширяет настройки столбца, полученные из хранилища
|
||||
* @param column Настройки столбца
|
||||
* @param name Имя столбца в случае его отсутствия в настройках
|
||||
* @returns Расширенные настройки столбца
|
||||
*/
|
||||
export const normalizeTableColumn = (column: TableColumnSettings, name?: string): TableColumnSettings => ({
|
||||
...column,
|
||||
columnName: column.columnName ?? name,
|
||||
visible: column.visible ?? true,
|
||||
})
|
||||
|
||||
/**
|
||||
* Подготавливает настройки столбца к записи в хранилище
|
||||
*
|
||||
* @param column Настройки столбца
|
||||
* @returns Подготовленные настройки столбца
|
||||
*/
|
||||
export const optimizeTableColumn = (column: TableColumnSettings): TableColumnSettings => ({
|
||||
...column,
|
||||
visible: column.visible ?? true,
|
||||
})
|
||||
|
||||
/**
|
||||
* Применяет настройки таблицы к списку столбцов
|
||||
*
|
||||
* @param columns Список столбцов таблицы
|
||||
* @param settings Объект настройки таблицы
|
||||
* @returns Список столбцов с настройками
|
||||
*/
|
||||
export const applyTableSettings = <T extends object>(columns: TableColumns<T>, settings: TableSettings): TableColumns<T> => {
|
||||
let newColumns: TableColumns<T> = columns.map((column) => ({ ...column }))
|
||||
newColumns = newColumns.filter((column) => {
|
||||
|
@ -3,7 +3,7 @@ import { useMemo } from 'react'
|
||||
import { ArgumentTypes } from '@utils/types'
|
||||
|
||||
/**
|
||||
* Значение типа может быть представлено непосредственно значением либо функцией его возвращаюшей
|
||||
* Значение типа может быть представлено непосредственно значением либо функцией его возвращающей
|
||||
*/
|
||||
export type ReturnType<T> = T extends (...args: any) => infer R ? R : any
|
||||
export type FunctionalValue<F extends Function> = ReturnType<F> | F
|
||||
@ -11,8 +11,8 @@ export type FunctionalValue<F extends Function> = ReturnType<F> | F
|
||||
export const getFunctionalValue = <F extends Function>(value: FunctionalValue<F>) => value instanceof Function ? value : ((...args: ArgumentTypes<F>) => value) as unknown as F
|
||||
|
||||
/**
|
||||
* Облегчает работу со значениями, которые могут быть представлены функциям.
|
||||
*
|
||||
* Облегчает работу со значениями, которые могут быть представлены функциям
|
||||
*
|
||||
* @param value Значение или функция его возвращающая
|
||||
* @returns Функция, вызов которой вернёт искомое значение
|
||||
*/
|
||||
|
@ -1,10 +1,16 @@
|
||||
export type TaskHandler<T> = () => T | PromiseLike<T>
|
||||
|
||||
export type Queue<T> = {
|
||||
/** Метод добавления задачи в очередь */
|
||||
push: (task: TaskHandler<T>) => Promise<T>
|
||||
/** Длина очереди */
|
||||
readonly length: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Фабрика очередей задач
|
||||
* @returns Очередь задач
|
||||
*/
|
||||
export const makeTaskQueue = <T,>(): Queue<T> => {
|
||||
let pending: Promise<T | void> = Promise.resolve()
|
||||
let count: number = 0
|
||||
|
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Объединить типы, исключив совпадающие поля справа.
|
||||
*
|
||||
*
|
||||
* @typeParam T - Тип, передаваемый полностью
|
||||
* @typeParam R - Аддитивный тип
|
||||
*
|
||||
*
|
||||
* @returns Общий тип с полным `T` и несовпадающими полями из `R`
|
||||
*/
|
||||
export type OmitExtends<T, R> = T & Omit<R, keyof T>
|
||||
|
Loading…
Reference in New Issue
Block a user