openreplay/frontend/app/utils/index.ts
Delirium 1326bb2eae
feat spot: init commit for extension (#2452)
* feat spot: init commit for extension

* nvmrc

* fix login flow

* Spots Gridview Updates (#2422)

* feat ui: login flow for spot extension

* spot list, store and service created

* some fixing for header

* start work on single spot

* spot player start

* header for player, comments, icons, etc

* split stuff into compoennts, create player state manager

* player controls, activity panel etc etc

* comments, empty page, rename and stuff

* interval buttons etc

* access modal

* pubkey support

* fix tooltip

* limit 10 -> 9

* hls lib instead of videojs

* some warnings

* fix date display for exp

* change public links

* display more client data

* fix cleaning, init comment

* map network to replay player network ev

* stream support, console panel, close panels on X

* fixing streaming, destroy on leave

* fix autoplay

* show notification on spot login

* fix spot login

* backup player added, fix audio issue

* show thumbnail when no video, add spot roles

* add poster thumbnail

* some fixes to video check

* fix events jump

* fix play btn

* try catch over pubkey

* feat ui: login flow for spot extension

* spot list, store and service created

* some fixing for header

* start work on single spot

* spot player start

* header for player, comments, icons, etc

* split stuff into compoennts, create player state manager

* player controls, activity panel etc etc

* comments, empty page, rename and stuff

* interval buttons etc

* access modal

* pubkey support

* fix tooltip

* limit 10 -> 9

* hls lib instead of videojs

* some warnings

* fix date display for exp

* change public links

* display more client data

* fix cleaning, init comment

* map network to replay player network ev

* stream support, console panel, close panels on X

* fixing streaming, destroy on leave

* fix autoplay

* show notification on spot login

* fix spot login

* backup player added, fix audio issue

* show thumbnail when no video, add spot roles

* add poster thumbnail

* some fixes to video check

* fix events jump

* fix play btn

* try catch over pubkey

* icons

* Various updates

* Update SVG.tsx

* Update SideMenu.tsx

* SpotList & Menu updates

* feat ui: login flow for spot extension

* spot list, store and service created

* some fixing for header

* start work on single spot

* spot player start

* header for player, comments, icons, etc

* split stuff into compoennts, create player state manager

* player controls, activity panel etc etc

* comments, empty page, rename and stuff

* interval buttons etc

* access modal

* pubkey support

* fix tooltip

* limit 10 -> 9

* hls lib instead of videojs

* some warnings

* fix date display for exp

* change public links

* display more client data

* fix cleaning, init comment

* map network to replay player network ev

* stream support, console panel, close panels on X

* fixing streaming, destroy on leave

* fix autoplay

* show notification on spot login

* fix spot login

* backup player added, fix audio issue

* show thumbnail when no video, add spot roles

* add poster thumbnail

* some fixes to video check

* fix events jump

* fix play btn

* try catch over pubkey

* icons

* spot login pinging

* Spot List & Player Updates

* move spot login flow to login comp, use separate spot login path for unique jwt

* invalidate spot jwt on logout

* add visual data on page load event

* typo fix

* Spot Listing improvements post review.

* Update SpotListItem.tsx

* Improved Spot List and Item Details

* Minor improvements

* More improvements

* Public player header improvements

* Moved formatExpirationTime to utils

* fixes after merge

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* set sso link to <a>?

* some small perf fixes

* login duck reformat...

* Update frontend.yaml

* add observer to spot list header

* split list header

* update spotjwt param in router

* fix toast in router

* fix async fetch, move ctx

* capture space btn ev

* fix header link

* public sharing error msg

* fix err msg for unsuccessful rec start

* fix list alignment

* Caching assets. Finally!!!

* fix typing in comment field

* add pubkey to comments, fix console jump btn

* no content comp

* change refresh token logic

* move thumbnail ts

* move thumbnail ts

* fix tab change

* switch up toggler

* early exit if no jwt present

* regenerate icons

* fix location str

* fix ctx

* change thumnail res, return autoplay for video player

* parse links in console rows, fix injected method parse?

* remove ts from js

* fix console parsing order?

* fixes for autoplay

* xray for spot player

* move to spot list after login;
esc to cancel;
fix signup link;
move ux commit

* kb sc for skipping; xray for spot ext

* track aborted requests

* tooltip for readability

* fixing empty state

* New blank state + various minor improvements (#2471)

* New blank state + various minor improvements

* apres merge

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* rm temp v

* init or card

* empty state debug

* empty state debug

* empty state debug

* fix initor img

* spotonly scope support

* Improved Spot dead-end pages (#2475)

* Improved Spot dead-end pages

* Initiate OpenReplay Setup and some more

* get scope changes

* fix crash

* scope upgrade/downgrade

* scope setup flow

* ping for backend

* upgrade wxt deps

* cancel ping int on expiration

* check rec status

* fix ping

* check video processing state

* check video processing state

* fix xray close, network highlight, fcp rounding

* update wxt, move open spot stuff to settings

* fix some history issues

* fix spot login flow

* fix spot login again

* fix spot login again

* don't send two requests

* limit messages for logged users

* limit messages for logged users

* fix public ignore

* microphone stuff

* microphone stuff

* Various improvements (#2509)

* Various improvements

- Updated icons in mic settings
- Included prefix in Spot title
- Save recording notification has been updated
- Other minor UI improvements

* Inline declaration of spot name field, and settings UI

* str f

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* UI changes in player header, spot list (#2510)

* Added UI elements in player page

- Badge with counts for comments
- Download and Delete dropdown in player
- Spot selection -- UI improvement

* Minor copy updates

* completing changes

---------

Co-authored-by: nick-delirium <nikita@openreplay.com>

* rm cmt

* fix cellmeasurer

* thumbnail dur

* fix download

* Minor fixes (#2512)

- Spot delete confirmation
- Spot comments UI update
- Minor copy updates

* limit number of notif messages

* add spot title to doc title, add cache groups for webpack

* drop mic controls from recording popup view

* fix for webpack compress

* fix for auto mic pickup

* change status banners

* move svgs around, remove undefined check

* refactor svgs

* fix timetable scaling

* fix error popup

* self contain css

* pre-select spot on spot onboarding

---------

Co-authored-by: Sudheer Salavadi <connect.uxmaster@gmail.com>
Co-authored-by: Rajesh Rajendran <rjshrjndrn@users.noreply.github.com>
2024-08-29 13:35:58 +02:00

503 lines
14 KiB
TypeScript

// @ts-nocheck
import chroma from 'chroma-js';
import * as htmlToImage from 'html-to-image';
import { SESSION_FILTER } from 'App/constants/storageKeys';
export const HOUR_SECS = 60 * 60;
export const DAY_SECS = 24 * HOUR_SECS;
export const WEEK_SECS = 7 * DAY_SECS;
export const formatExpirationTime = (seconds: number) => {
if (seconds >= WEEK_SECS) {
return `${Math.floor(seconds / DAY_SECS)} days`;
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
return `${hours > 0 ? `${hours}h` : ''}${minutes > 0 ? `${minutes}m` : ''}`.trim();
};
export function debounce(callback, wait, context = this) {
let timeout = null;
let callbackArgs = null;
const later = () => callback.apply(context, callbackArgs);
return function (...args) {
callbackArgs = args;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/* eslint-disable no-mixed-operators */
export function randomInt(a, b) {
const min = (b ? a : 0) - 0.5;
const max = b || a || Number.MAX_SAFE_INTEGER;
const rand = min - 0.5 + Math.random() * (max - min + 1);
return Math.round(rand);
}
export const fileNameFormat = (str = '', ext = '') => {
const name = str.replace(/[^a-zA-Z0-9]/g, '_');
return `${name}${ext}`;
};
export const toUnderscore = (s) =>
s
.split(/(?=[A-Z])/)
.join('_')
.toLowerCase();
export const getUniqueFilter = (keys) => (item, i, list) =>
!list.some(
(item2, j) => j < i && keys.every((key) => item[key] === item2[key] && item[key] !== undefined)
);
export const numberWithCommas = (x) => (x ? x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : 0);
export const numberCompact = (x) => {
if (x < 1000) {
return x;
}
if (x < 1000000) {
return `${Math.floor(x / 1000)}K`;
}
return `${Math.floor(x / 1000000)}M`;
}
export const cutURL = (url, prefix = '.../') => `${prefix + url.split('/').slice(3).join('/')}`;
export const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export function getRE(string: string, options: string) {
let re;
try {
re = new RegExp(string, options);
} catch (e) {
re = new RegExp(escapeRegExp(string), options);
}
return re;
}
export const filterList = <T extends Record<string, any>>(
list: T[],
searchQuery: string,
testKeys: string[],
searchCb?: (listItem: T, query: RegExp) => boolean
): T[] => {
if (searchQuery === '') return list;
const filterRE = getRE(searchQuery, 'i');
let _list = list.filter((listItem: T) => {
return testKeys.some((key) => filterRE.test(listItem[key])) || searchCb?.(listItem, filterRE);
});
return _list;
};
export const getStateColor = (state) => {
switch (state) {
case 'passed':
return 'green';
case 'failed':
return 'red';
default:
return 'gray-medium';
}
};
export const convertNumberRange = (oldMax, oldMin, newMin, newMax, currentValue) => {
let newValue;
let newRange;
const oldRange = oldMax - oldMin;
if (oldRange === 0) {
newValue = newMin;
} else {
newRange = newMax - newMin;
newValue = ((currentValue - oldMin) * newRange) / oldRange + newMin;
}
return newValue;
};
export const prorata = ({ parts, elements, startDivisorFn, divisorFn }) => {
const byElement = Object.entries(elements).reduce(
(ack, [element, numElements]) => ({
...ack,
[element]: { parts: 0, elements: numElements, divisor: startDivisorFn(numElements) },
}),
{}
);
while (parts > 0) {
const element = Object.entries(byElement).reduce(
(a, [k, v]) => (a.divisor > v.divisor ? a : v),
{ divisor: 0 }
);
// eslint-disable-next-line no-plusplus
element.parts++;
element.divisor = divisorFn(element.elements, element.parts);
// eslint-disable-next-line no-plusplus
parts--;
}
return Object.entries(byElement).reduce((a, [k, v]) => ({ ...a, [k]: v.parts }), {});
};
export const titleCase = (str) => {
str = str.toLowerCase();
str = str.split('_');
for (var i = 0; i < str.length; i++) {
str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1);
}
return str.join(' ');
};
export const confirm = (message, callback) => {
const sure = window.confirm(message);
if (!sure) return;
callback();
};
const KB = 1 << 10;
const MB = KB << 10;
const GB = MB << 10;
export function formatBytes(bt: number): string {
if (bt > GB) {
return `${Math.trunc((bt / GB) * 1e2) / 1e2}GB`;
}
if (bt > MB) {
return `${Math.trunc((bt / MB) * 1e2) / 1e2}MB`;
}
if (bt > KB) {
return `${Math.trunc((bt / KB) * 1e2) / 1e2}KB`;
}
return `${Math.trunc(bt)}B`;
}
export function percentOf(part: number, whole: number): number {
return whole > 0 ? (part * 100) / whole : 0;
}
export function fileType(url: string) {
const filename = url.split(/[#?]/);
if (!filename || filename.length == 0) return '';
const parts = filename[0].split('.');
if (!parts || parts.length == 0) return '';
return parts.pop().trim();
}
export function fileName(url: string) {
if (url) {
var m = url.toString().match(/.*\/(.+?)\./);
if (m && m.length > 1) {
return `${m[1]}.${fileType(url)}`;
}
}
return '';
}
export const camelCased = (val) =>
val.replace(/_([a-z])/g, function (g) {
return g[1].toUpperCase();
});
export function capitalize(s: string) {
if (!s || !s.length) return s;
return s[0].toUpperCase() + s.slice(1);
}
export const titleize = (str) => {
let upper = true;
let newStr = '';
for (let i = 0, l = str.length; i < l; i++) {
// Note that you can also check for all kinds of spaces with
// str[i].match(/\s/)
if (str[i] == ' ') {
upper = true;
newStr += str[i];
continue;
}
if (str[i] == '_') {
upper = true;
newStr += ' ';
continue;
}
newStr += upper ? str[i].toUpperCase() : str[i].toLowerCase();
upper = false;
}
return newStr;
};
export const colorScale = (values, colors) => {
return chroma.scale(colors);
};
export const truncate = (input, max = 10) =>
input.length > max ? `${input.substring(0, max)}...` : input;
export const iceServerConfigFromString = (str) => {
if (!str || typeof str !== 'string' || str.length === 0) {
return null;
}
return str.split('|').map(function (c) {
let server = null;
const arr = c.split(',');
if (!!arr[0] !== '') {
server = {};
server.urls = arr[0];
if (!!arr[1]) {
server.username = arr[1];
if (!!arr[2]) {
server.credential = arr[2];
}
}
return server;
}
});
};
export const isGreaterOrEqualVersion = (version, compareTo) => {
const [major, minor, patch] = version.split('-')[0].split('.');
const [majorC, minorC, patchC] = compareTo.split('-')[0].split('.');
return (
major > majorC ||
(major === majorC && minor > minorC) ||
(major === majorC && minor === minorC && patch >= patchC)
);
};
export const sliceListPerPage = <T extends Array<any>>(list: T, page: number, perPage = 10): T => {
const start = page * perPage;
const end = start + perPage;
return list.slice(start, end) as T;
};
export const positionOfTheNumber = (min, max, value, length) => {
const interval = (max - min) / length;
const position = Math.round((value - min) / interval);
return position;
};
export const convertElementToImage = async (el: HTMLElement) => {
// const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el);
const image = await htmlToImage.toJpeg(el, {
pixelRatio: 2,
// fontEmbedCss,
filter: function (node) {
return node.id !== 'no-print';
},
});
return image;
};
export const unserscoreToSpaceAndCapitalize = (str) => {
return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
export const convertToCSV = (headers, objArray) => {
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
var str = '';
const headersMap = headers.reduce((acc, curr) => {
acc[curr.key] = curr;
return acc;
}, {});
str += headers.map((h) => h.label).join(',') + '\r\n';
for (var i = 0; i < array.length; i++) {
var line = '';
for (var index in headersMap) {
if (line !== '') line += ',';
line += array[i][index];
}
str += line + '\r\n';
}
return str;
};
export const exportCSVFile = (headers, items, fileTitle) => {
var jsonObject = JSON.stringify(items);
var csv = convertToCSV(headers, jsonObject);
var exportedFilenmae = fileTitle + '.csv' || 'export.csv';
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) {
// IE 10+
navigator.msSaveBlob(blob, exportedFilenmae);
} else {
var link = document.createElement('a');
if (link.download !== undefined) {
var url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', exportedFilenmae);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
export const cleanSessionFilters = (data: any) => {
const { filters, ...rest } = data;
const _fitlers = filters.filter((f: any) => {
if (f.operator === 'isAny' || f.operator === 'onAny') {
return true;
} // ignore filter with isAny/onAny operator
if (Array.isArray(f.filters) && f.filters.length > 0) {
return true;
} // ignore subfilters
return f.value !== '' && Array.isArray(f.value) && f.value.length > 0;
});
return { ...rest, filters: _fitlers };
};
export const getSessionFilter = () => {
return JSON.parse(localStorage.getItem(SESSION_FILTER));
};
export const setSessionFilter = (filter: any) => {
localStorage.setItem(SESSION_FILTER, JSON.stringify(filter));
};
export const compareJsonObjects = (obj1: any, obj2: any) => {
return JSON.stringify(obj1) === JSON.stringify(obj2);
};
export const getInitials = (name: any) => {
const names = name.split(' ');
return (
names
.slice(0, 2)
.map((n: any) => n[0]?.toUpperCase())
.join('') || ''
);
};
export function getTimelinePosition(value: any, scale: any) {
const pos = value * scale;
return pos > 100 ? 100 : pos;
}
export function millisToMinutesAndSeconds(millis: any) {
const minutes = Math.floor(millis / 60000);
const seconds: any = ((millis % 60000) / 1000).toFixed(0);
return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's';
}
export function simpleThrottle(func: (...args: any[]) => void, limit: number): (...args: any[]) => void {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
export function throttle(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function () {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
export function deleteCookie(name: string, path: string, domain: string) {
document.cookie =
name +
'=' +
(path ? ';path=' + path : '') +
(domain ? ';domain=' + domain : '') +
';expires=Thu, 01 Jan 1970 00:00:01 GMT';
}
/**
* Checks if a specified query parameter exists in the URL and if its value is set to 'true'.
* If a storageKey is provided, stores the result in localStorage under that key.
*
* @function
* @param {string} paramName - The name of the URL parameter to check.
* @param {string} [storageKey] - The optional key to use for storing the result in localStorage.
* @param search
* @returns {boolean} - Returns true if the parameter exists and its value is 'true'. Otherwise, returns false.
*
* @example
* // Assuming URL is: http://example.com/?iframe=true
* const isIframeEnabled = checkParam('iframe'); // Returns true, doesn't store in localStorage
* const isIframeEnabledWithStorage = checkParam('iframe', 'storageKey'); // Returns true, stores in localStorage
*
* @description
* The function inspects the current URL's query parameters. If the specified parameter exists
* and its value is set to 'true', and a storageKey is provided, the function stores 'true' under
* the provided storage key in the localStorage. If the condition is not met, or if the parameter
* does not exist, and a storageKey is provided, any existing localStorage entry with the storageKey
* is removed.
*/
export const checkParam = (paramName: string, storageKey?: string, search?: string): boolean => {
const urlParams = new URLSearchParams(search ? search : window.location.search);
const paramValue = urlParams.get(paramName);
const existsAndTrue = paramValue && paramValue === 'true' || paramValue?.length > 0;
if (storageKey) {
if (existsAndTrue) {
localStorage.setItem(storageKey, 'true');
} else {
localStorage.removeItem(storageKey);
}
}
return existsAndTrue;
};
export const isValidUrl = (url) => /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/.test(url);
export function truncateStringToFit(string: string, screenWidth: number, charWidth: number = 5): string {
if (string.length * charWidth <= screenWidth) {
return string;
}
const ellipsis = '...';
const maxLen = Math.floor(screenWidth / charWidth);
if (maxLen <= ellipsis.length) {
return ellipsis.slice(0, maxLen);
}
const frontLen = Math.floor((maxLen - ellipsis.length) / 2);
const backLen = maxLen - ellipsis.length - frontLen;
return string.slice(0, frontLen) + ellipsis + string.slice(-backLen);
}