maintenance

This commit is contained in:
2025-09-04 20:25:15 -04:00
parent 1469c7f52f
commit e8f2df9e69
214 changed files with 8507 additions and 1836 deletions

6
src/utility/contexts.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import {createContext} from 'react';
export const ViewContext = createContext('');

View File

@@ -0,0 +1,20 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import countryMapping from 'services/files/countries_mapping.json';
export const getCountryFullName = (shortName: string): string => {
return countryMapping[shortName] || 'Unknown';
};
// Matches Java's implementation of the standard object.hashCode() for CharSequence
export const createHashFromString = (string: string): number => {
let hash = 0;
let i = 0;
while (i < string.length) {
hash = ((hash << 5) - hash + string.charCodeAt(i++)) << 0;
}
return hash;
};

105
src/utility/custom-hooks.ts Normal file
View File

@@ -0,0 +1,105 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import {useState, useEffect, useRef, Dispatch, SetStateAction, RefObject} from 'react';
import {DirectionType, ITableSort} from 'interfaces/tableProps';
import AuthService from 'services/Authentication.service';
const getWidth = () => window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
export const useCurrentWidth = (): number => {
const [width, setWidth] = useState(getWidth());
useEffect(() => {
const resizeListener = () => setWidth(getWidth());
window.addEventListener('resize', resizeListener);
return () => window.removeEventListener('resize', resizeListener);
}, []);
return width;
};
export const useComponentVisible = (
initialIsVisible: boolean
): {
ref: RefObject<HTMLDivElement>;
isComponentVisible: boolean;
setIsComponentVisible: Dispatch<SetStateAction<boolean>>;
} => {
const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
const ref = useRef(null);
const handleClickOutside = () => {
setIsComponentVisible(false);
};
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, []);
return {
ref,
isComponentVisible,
setIsComponentVisible
};
};
export const useHeightResize = (heightOffset: number): [number] => {
const [height, setHeight] = useState(window.innerHeight - heightOffset);
useEffect(() => {
const handleResize = () => {
setHeight(window.innerHeight - heightOffset);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
// eslint-disable-next-line
}, []);
return [height];
};
export const useSearch = (propsSearchValue?: string): [string, Dispatch<SetStateAction<string>>] => {
const [searchValue, setSearchValue] = useState<string | undefined>(propsSearchValue);
useEffect(() => {
setSearchValue(propsSearchValue);
}, [propsSearchValue, setSearchValue]);
return [searchValue, setSearchValue];
};
export const useSort = ({sortColumn, sortDirection}: Partial<ITableSort>): [ITableSort, Dispatch<SetStateAction<ITableSort>>] => {
const [sortData, setSortData] = useState<ITableSort | null>(null);
useEffect(() => {
if ((sortColumn && !sortData) || sortColumn !== sortData?.sortColumn || sortDirection !== sortData?.sortDirection) {
const newSortDirection = sortDirection || sortData?.sortDirection || DirectionType.Asc;
const newSortColumn = sortColumn || sortData?.sortColumn;
setSortData({
sortDirection: newSortDirection,
sortColumn: newSortColumn ?? ''
});
}
}, [sortColumn, sortDirection, sortData, setSortData]);
return [sortData, setSortData];
};
export const useLoginStatus = (applicationId: string): [boolean] => {
const [isLoggedIn, setLoginStatus] = useState(false);
useEffect(() => {
AuthService.hasLoginToken().then((response: boolean) => setLoginStatus(response));
}, [applicationId]);
return [isLoggedIn];
};

129
src/utility/date.ts Normal file
View File

@@ -0,0 +1,129 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import moment, {DurationInputArg2, Moment, unitOfTime} from 'moment';
import AuthService from 'services/Authentication.service';
import userStore from 'services/user-store';
import {DataValueType} from 'components/table';
let delta = 0;
export enum TimeFormats {
Utc = 'utc',
LocalTime = 'localTime'
}
export const defaultTimeFormat = `MM-DD-YYYY HH:mm:ss`;
export const isoTimeFormat = `YYYY-MM-DD HH:mm:ss`;
export const shortTimeFormat = `YYYY-MM-DD`;
export const getDuration = (start: Date, end: Date, unitOfTime: unitOfTime.Diff): number => {
return moment.utc(end).diff(moment.utc(start), unitOfTime);
};
export const fetchPlatformTimeFromServer = async (): Promise<void> => {
let platformTime;
const localTimeBeforeCall = moment.utc().utc().valueOf();
const headers = new Headers();
headers.set('Authorization', `Basic ${await userStore.get(AuthService.token, '')}`);
const response = await fetch('https://phenixrts.com/video/dash/time.txt', {headers});
const localTimeAfterCall = moment.utc().utc().valueOf();
const localTime = localTimeBeforeCall + (localTimeAfterCall - localTimeBeforeCall) / 2;
const dateHeader = response.headers.get('date');
if (dateHeader) {
platformTime = moment.utc(dateHeader).utc();
delta = moment.duration(platformTime.diff(localTime)).asMilliseconds();
}
};
export const getAdjustedTime = (): Moment => {
return moment.utc().add(delta, 'milliseconds').milliseconds(0);
};
export const getTimezoneAbbreviation = (date: Date): string => {
const timezone = date
.toString()
.match(/\(.+\)/g)[0]
.replace('(', '')
.replace(')', '');
let abbreviation = '';
timezone.split(' ').forEach(word => {
abbreviation += word.split('')[0];
});
return abbreviation;
};
export const formatDate = (date: Moment | string | Date, format: TimeFormats): string => {
const isUTC = format === TimeFormats.Utc;
const utcDate = moment.utc(date).format(`${isoTimeFormat} UTC`);
const localDate = `${moment.utc(date).format(isoTimeFormat)} ${getTimezoneAbbreviation(moment.utc(date).toDate())}`;
return isUTC ? utcDate : localDate;
};
export const getFormattedDate = (val: boolean | string | number, format: string = defaultTimeFormat): string => {
// TODO: Change table component, as value type in this function should not be a boolean type
if (!!val && (typeof val === 'number' || typeof val === 'string')) {
return moment.utc(val).format(format);
}
return '';
};
export const getDateFromNow = (value: DataValueType): string => {
return value ? moment.utc(value).fromNow() : '';
};
export const getDurationFromNow = (val: number, type: DurationInputArg2 = 'seconds'): number => {
const now = moment.utc(new Date());
const end = moment.utc(val);
const duration = end.diff(now, type);
return duration;
};
export const getMinAndMaxFromArrayOfObject = (
// TODO: move out this function, as it is not related to dates
arr: Record<string, any>[], // eslint-disable-line
prop: string,
mutate?: (val) => string | number
): {min: string | number; max: string | number} | null => {
const arrLength = arr.length;
if (arrLength) {
let min = mutate ? mutate(arr[0][prop]) : arr[0][prop];
let max = mutate ? mutate(arr[0][prop]) : arr[0][prop];
let minIdx = 0;
let maxIdx = 0;
let tmp;
for (let i = 0; i < arrLength; i += 1) {
tmp = mutate ? mutate(arr[i][prop]) : arr[i][prop];
if (tmp < min) {
min = tmp;
minIdx = i;
}
if (tmp > max) {
max = tmp;
maxIdx = i;
}
}
return {
min: arr[minIdx][prop],
max: arr[maxIdx][prop]
};
}
return {
min: null,
max: null
};
};

View File

@@ -0,0 +1,76 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export interface IError extends Error {
response?: {
status?: number;
data?: {
status?: string;
message?: string;
};
config?: {
data?: string;
};
};
}
export interface IPortalError {
status: string;
message?: string;
statusCode?: number;
requestPayload?: string;
}
export class PortalError extends Error implements IPortalError {
private _name: string;
private _status: string;
private _message?: string;
private _statusCode?: number;
private _requestPayload?: string;
constructor(status?: string, options: {message?: string; statusCode?: number; requestPayload?: string} = {}) {
super(options.message || status);
this._name = 'PortalError'; // Set the error name
this._status = status || 'default'; // Custom status property
this._message = options.message;
this._statusCode = options.statusCode;
this._requestPayload = options.requestPayload;
}
get status(): string {
return this._status;
}
get name(): string {
return this._name;
}
get message(): string | undefined {
return this._message;
}
get statusCode(): number | undefined {
return this._statusCode;
}
get requestPayload(): string | undefined {
return this._requestPayload;
}
}
export const transformToPortalError = (e: Error | IPortalError | string | unknown): PortalError => {
if (e instanceof PortalError) {
return e;
}
if (e instanceof Error) {
return new PortalError('no-status-error', {message: e.message});
}
if (typeof e === 'string') {
return new PortalError('no-status-error', {message: e});
}
return new PortalError();
};

40
src/utility/filters.ts Normal file
View File

@@ -0,0 +1,40 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import {IAdvancedSelectItem} from 'components/ui/advanced-select';
import {capabilities} from 'constants/capabilities';
import {isEqual} from './validators';
export const mergeArrays = <T>(firstArray: T[], secondArray: T[]): T[] => {
// This method is generic and we accept any types of arrays for it
const longer = firstArray.length >= secondArray.length ? firstArray : secondArray;
const shorter = secondArray.length <= firstArray.length ? secondArray : firstArray;
const difference = [];
shorter.forEach(recordInLongerArray => {
const isPresent = longer.some(recordInShorterArray => isEqual(recordInShorterArray, recordInLongerArray));
if (!isPresent) {
difference.push(recordInLongerArray);
}
});
return [...difference, ...longer];
};
export const findDefaultCapabilities = (...requestedCapabilities: string[]): IAdvancedSelectItem[] => {
const foundCapabilities = new Map();
for (const capability of capabilities) {
if (requestedCapabilities.includes(capability.value) && !foundCapabilities.has(capability.value)) {
foundCapabilities.set(capability.value, capability);
if (foundCapabilities.size === requestedCapabilities.length) {
break;
}
}
}
return Array.from(foundCapabilities.values());
};

9
src/utility/index.ts Normal file
View File

@@ -0,0 +1,9 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export * from './contexts';
export * from './date';
export * from './sort';
export * from './validators';
export * from './conversions';
export * from './stream';

93
src/utility/numbers.ts Normal file
View File

@@ -0,0 +1,93 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export const toSeconds = (milliseconds: number): number => {
if (!milliseconds) {
return 0;
}
return milliseconds / 1000;
};
export const toSignificantFigure = (value: number, precision = 3): string => {
return value.toPrecision(precision);
};
export const formatNumberWithPrecision = (value: number, precision = 1): string => {
if (!value && value !== 0) {
return '';
}
if (value % 1 !== 0) {
value = Number(value.toFixed(precision));
}
return value.toLocaleString();
};
export const addTrailingZeroToPercentage = (stringNumber: string): string => {
if (!stringNumber || stringNumber === '%') {
return '';
}
if (stringNumber.indexOf('.') > -1) {
return stringNumber;
}
return stringNumber.replace('%', '.0%');
};
export const formatToLocalString = (value: string | number): string => {
return Number(value).toLocaleString();
};
export const formatWithUnits = (value: number, valueUnits: string): string => {
const sizeUnits = [
{
value: 1,
symbol: ''
},
{
value: 1e3,
symbol: 'k'
},
{
value: 1e6,
symbol: 'M'
},
{
value: 1e9,
symbol: 'G'
},
{
value: 1e12,
symbol: 'T'
},
{
value: 1e15,
symbol: 'P'
},
{
value: 1e18,
symbol: 'E'
}
];
const valueSize =
sizeUnits
.slice()
.reverse()
.find(unit => {
return value >= unit.value;
}) || sizeUnits[0];
let formatedValue = (value / valueSize.value).toFixed(2) || 0;
if (valueSize !== sizeUnits[0]) {
formatedValue = String(formatedValue).substring(0, 3);
while (formatedValue.replace('.', '').length !== 3) {
formatedValue += '0';
}
}
return `${formatedValue}${valueSize.symbol}${valueUnits}`;
};

39
src/utility/polyfills.ts Normal file
View File

@@ -0,0 +1,39 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
if (!ReadableStream.prototype[Symbol.asyncIterator]) {
Object.defineProperty(ReadableStream.prototype, Symbol.asyncIterator, {
value: function () {
const reader = this.getReader();
return {
next: async () => {
const {done, value} = await reader.read();
return {
value,
done
};
},
return: async () => {
reader.releaseLock();
return {
value: undefined,
done: true
};
},
throw: async e => {
reader.releaseLock();
throw e;
},
[Symbol.asyncIterator]: function () {
return this;
}
};
},
writable: true,
configurable: true
});
}

20
src/utility/sort.ts Normal file
View File

@@ -0,0 +1,20 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import {DirectionType} from 'interfaces/tableProps';
import {DataValueType} from 'components/table';
export const compare = (a: Record<string, DataValueType>, b: Record<string, DataValueType>, direction: DirectionType, propName: string): number => {
if (a && b) {
const isDesc = direction === DirectionType.Desc;
if (a[propName] < b[propName]) {
return isDesc ? -1 : 1;
} else if (a[propName] > b[propName]) {
return isDesc ? 1 : -1;
}
}
return 0;
};

40
src/utility/stream.ts Normal file
View File

@@ -0,0 +1,40 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export const parseStreamIdFromStreamUri = (uri: string): string => {
const indexOfQuestionMark = uri.indexOf('?');
const parsedUri = uri?.split('/') || [];
const [protocol, , domain] = parsedUri;
const streamUriPrefix = protocol && domain ? `${protocol}//${domain}/` : '';
return uri.substring(streamUriPrefix.length, indexOfQuestionMark > -1 ? indexOfQuestionMark : uri.length);
};
interface IStreamInfo {
streamId?: string;
capabilities?: string[];
}
export const parseStreamInfoFromStreamUri = (uri: string): IStreamInfo => {
if (!uri) {
return;
}
const streamId = parseStreamIdFromStreamUri(uri);
const queryParams = [...new URLSearchParams(uri.split('?')[1])].reduce((accumulator, [key, value]) => {
if (key === 'capabilities') {
accumulator[key] = value?.split(',') || [];
return accumulator;
}
accumulator[key] = value;
return accumulator;
}, {});
return {
...queryParams,
streamId
};
};

81
src/utility/validators.ts Normal file
View File

@@ -0,0 +1,81 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export const validateURL = (str: string): boolean => {
let url;
try {
url = new URL(str);
} catch (ignoredErr) {
return false;
}
return !!url.protocol;
};
export const isEqual = (obj1: unknown, obj2: unknown): boolean => {
// In this case we really accepting any types of arguments
// TODO: Replace isEqual usage from components/shared/utils with this one
const getType = (obj: unknown): string => {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
};
const areArraysEqual = () => {
if (obj1.length !== obj2.length) {
return false;
}
for (let i = 0; i < obj1.length; i++) {
if (!isEqual(obj1[i], obj2[i])) {
return false;
}
}
return true;
};
const areObjectsEqual = () => {
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false;
}
for (const key in obj1) {
if (Object.prototype.hasOwnProperty.call(obj1, key)) {
if (!isEqual(obj1[key], obj2[key])) {
return false;
}
}
}
return true;
};
const areFunctionsEqual = () => {
return obj1.toString() === obj2.toString();
};
const arePrimativesEqual = () => {
return obj1 === obj2;
};
const type = getType(obj1);
if (type !== getType(obj2)) {
return false;
}
if (type === 'array') {
return areArraysEqual();
}
if (type === 'object') {
return areObjectsEqual();
}
if (type === 'function') {
return areFunctionsEqual();
}
return arePrimativesEqual();
};