Files
pezkuwi-apps/packages/apps/src/Endpoints/utils.ts
T
Satoshi Qazi Muhammed 45d479d2ec Move to @pezkuwi/dev 0.86.4 (eslint 10 toolchain)
- dev ^0.86.4 at the root and in packages/apps; directives renamed by
  pezkuwi-dev-migrate-eslint10.
- eslint --fix with the 0.86.4 config; one fix broke syntax
  (typescript-eslint prefer-optional-chain turned
  `(isStarted && onStart) && onStart()` into `(isStarted && onStart?.();`
  in TxButton) and is corrected by hand.
- The remaining findings fixed rather than disabled: optional chains
  (each suggestion reviewed), `cause` on rethrown errors, `if` instead of
  `a && promise` in effects, parallel instead of sequential awaits in
  Promise.all, Error rejections, no `delete` on arrays (the storage query
  cache is a Map; an upload's file list no longer keeps a hole), no
  `[object Object]` stringification (TFunction's second argument is text
  or options), dead code removed (referenda's never-shown expand button,
  react-query's unused CoreDescriptor).
2026-10-10 02:06:12 +04:00

157 lines
4.4 KiB
TypeScript

// Copyright 2017-2026 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { IFavoriteChainProps, IFavoriteChainsStorage } from './types.js';
import { createWsEndpoints } from '@pezkuwi/apps-config';
export const FAVORITE_CHAINS_KEY = 'pezkuwi-app-favorite-chains';
const chainsConfig = createWsEndpoints((k, v) => (typeof v === 'string' && v) || k);
export const toggleFavoriteChain = (
chainInfo: IFavoriteChainProps
) => {
try {
const chainName = chainInfo.chainName;
const meta = { paraId: chainInfo.paraId ?? -1, relay: chainInfo.relay ?? 'Unknown' };
const favoriteChains = getFavoriteChains();
const existingEntries = favoriteChains[chainName] ?? [];
const alreadyExists = existingEntries.some(
(entry) => entry.relay === meta.relay && entry.paraId === meta.paraId
);
let updatedEntries: IFavoriteChainsStorage[string];
if (alreadyExists) {
// Remove the matching entry
updatedEntries = existingEntries.filter(
(entry) => entry.relay !== meta.relay || entry.paraId !== meta.paraId
);
} else {
// Add new entry
updatedEntries = [...existingEntries, meta];
}
const updatedChains: IFavoriteChainsStorage = { ...favoriteChains };
if (updatedEntries.length === 0) {
delete updatedChains[chainName];
} else {
updatedChains[chainName] = updatedEntries;
}
localStorage.setItem(FAVORITE_CHAINS_KEY, JSON.stringify(updatedChains));
} catch {}
};
export const getFavoriteChains = (): IFavoriteChainsStorage => {
try {
const favoriteChains = localStorage.getItem(FAVORITE_CHAINS_KEY);
if (!favoriteChains) {
return {};
}
const parsed: unknown = JSON.parse(favoriteChains);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
throw new Error('Invalid favorite chains format');
}
const result: IFavoriteChainsStorage = {};
for (const [key, value] of Object.entries(parsed) as [string, IFavoriteChainsStorage[string]][]) {
if (!Array.isArray(value)) {
throw new Error(`Invalid value for key "${key}": not an array`);
}
const allValid = value.every(
(entry: IFavoriteChainsStorage[string][number]) =>
typeof entry === 'object' &&
entry !== null &&
typeof entry.relay === 'string' &&
typeof entry.paraId === 'number'
);
if (!allValid) {
throw new Error(`Invalid entries under key "${key}"`);
}
// Make sure key exists in chain config
if (chainsConfig.find((e) => e.text === key)) {
// Make sure "relay" value also exists in chain config
const matchingFavorites = value.filter((v) => v.relay === 'Unknown' ? true : !!chainsConfig.find((e) => e.text === v.relay));
if (matchingFavorites.length > 0) {
result[key] = matchingFavorites;
}
}
}
// Set filtered result to localStorage
localStorage.setItem(FAVORITE_CHAINS_KEY, JSON.stringify(result));
return result;
} catch (e) {
console.error('Failed to parse favorite chains:', e);
localStorage.removeItem(FAVORITE_CHAINS_KEY);
return {};
}
};
export const isFavoriteChain = (
favoriteChains: IFavoriteChainsStorage,
chainInfo: IFavoriteChainProps
): boolean => {
try {
const chainName = chainInfo.chainName;
const meta = { paraId: chainInfo.paraId ?? -1, relay: chainInfo.relay ?? 'Unknown' };
const list = favoriteChains[chainName];
if (!Array.isArray(list)) {
return false;
}
return list.some(
(entry) =>
entry.relay === meta.relay && entry.paraId === meta.paraId
);
} catch (e) {
console.error('Failed to check favorite chain:', e);
return false;
}
};
export function getContrastingColor (hexColor: string): string {
if (typeof hexColor !== 'string') {
return '#000000';
}
let hex = hexColor.replace('#', '').trim();
if (hex.length === 3) {
hex = hex.split('').map((c) => c + c).join('');
}
if (hex.length !== 6 || /[^0-9a-f]/i.test(hex)) {
return '#000000';
}
try {
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? '#000000' : '#FFFFFF';
} catch {
return '#000000';
}
}