169 lines
4.7 KiB
TypeScript
169 lines
4.7 KiB
TypeScript
import { type ClassValue, clsx } from 'clsx';
|
|
import { twMerge } from 'tailwind-merge';
|
|
import type { TimeRemaining } from './types';
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
// https://perryjanssen.medium.com/getting-random-tracks-using-the-spotify-api-61889b0c0c27
|
|
export function getRandomSearch() {
|
|
// A list of all characters that can be chosen.
|
|
const characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
|
|
// Gets a random character from the characters string.
|
|
const randomCharacter = characters.charAt(Math.floor(Math.random() * characters.length));
|
|
let randomSearch = '';
|
|
|
|
// Places the wildcard character at the beginning, or both beginning and end, randomly.
|
|
switch (Math.round(Math.random())) {
|
|
case 0:
|
|
randomSearch = randomCharacter + '%';
|
|
break;
|
|
case 1:
|
|
randomSearch = '%' + randomCharacter + '%';
|
|
break;
|
|
}
|
|
|
|
return randomSearch;
|
|
}
|
|
|
|
// Created using Claude 3.5 Sonett
|
|
export function shuffleObjectValues<T extends object>(arr: Array<T>): Array<T> {
|
|
// Create a copy of the array
|
|
const copy = structuredClone(arr);
|
|
|
|
// Get all keys from the first object
|
|
const keys = Object.keys(copy[0] as object);
|
|
|
|
keys.forEach((key: string) => {
|
|
// Get all values for this key with proper type assertion
|
|
const values = copy.map((obj) => (obj as { [key: string]: unknown })[key]);
|
|
|
|
// Fisher-Yates shuffle algorithm
|
|
for (let i = values.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[values[i], values[j]] = [values[j], values[i]];
|
|
}
|
|
|
|
// Reassign shuffled values back to objects
|
|
copy.forEach((obj, index) => {
|
|
(obj as { [key: string]: unknown })[key] = values[index];
|
|
});
|
|
});
|
|
|
|
return copy;
|
|
}
|
|
|
|
// https://stackoverflow.com/a/12646864
|
|
export function shuffleArray<T>(array: T[]): T[] {
|
|
for (let i = array.length - 1; i >= 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
// Created using Claude 3.5 Sonett
|
|
export function truncate(text: string, maxLength: number): string {
|
|
// Return original text if it's shorter than or equal to maxLength
|
|
if (text.length <= maxLength) {
|
|
return text;
|
|
}
|
|
|
|
const ellipsis = '…';
|
|
const tolerance = 5;
|
|
const targetLength = maxLength - 1; // Account for ellipsis
|
|
|
|
// Look for spaces within the tolerance range before targetLength
|
|
let spaceBeforeIdx = -1;
|
|
for (let i = targetLength; i >= targetLength - tolerance; i--) {
|
|
if (text[i] === ' ') {
|
|
spaceBeforeIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Look for spaces within the tolerance range after targetLength
|
|
let spaceAfterIdx = -1;
|
|
for (let i = targetLength; i <= targetLength + tolerance && i < text.length; i++) {
|
|
if (text[i] === ' ') {
|
|
spaceAfterIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Determine the best cutoff point
|
|
let cutoffIndex = targetLength;
|
|
if (spaceBeforeIdx !== -1 && spaceAfterIdx !== -1) {
|
|
// If we found spaces both before and after, use the closest one
|
|
cutoffIndex =
|
|
targetLength - spaceBeforeIdx <= spaceAfterIdx - targetLength
|
|
? spaceBeforeIdx
|
|
: spaceAfterIdx;
|
|
} else if (spaceBeforeIdx !== -1) {
|
|
cutoffIndex = spaceBeforeIdx;
|
|
} else if (spaceAfterIdx !== -1) {
|
|
cutoffIndex = spaceAfterIdx;
|
|
}
|
|
|
|
return text.slice(0, cutoffIndex).trim() + ellipsis;
|
|
}
|
|
|
|
// Created using Claude 3.5 Sonett
|
|
export function getTimeRemaining(currentTime: Date, targetTime: Date): TimeRemaining {
|
|
// Convert both dates to milliseconds and get the difference
|
|
let delta = targetTime.getTime() - currentTime.getTime();
|
|
|
|
// If target is in the past, return all zeros
|
|
if (delta < 0) {
|
|
return {
|
|
years: 0,
|
|
months: 0,
|
|
days: 0,
|
|
hours: 0,
|
|
minutes: 0,
|
|
seconds: 0,
|
|
milliseconds: 0
|
|
};
|
|
}
|
|
|
|
// Calculate each unit
|
|
const millisecondsPerSecond = 1000;
|
|
const millisecondsPerMinute = millisecondsPerSecond * 60;
|
|
const millisecondsPerHour = millisecondsPerMinute * 60;
|
|
const millisecondsPerDay = millisecondsPerHour * 24;
|
|
const millisecondsPerMonth = millisecondsPerDay * 30.436875; // Average month length
|
|
const millisecondsPerYear = millisecondsPerDay * 365.2425; // Average year length
|
|
|
|
const years = Math.floor(delta / millisecondsPerYear);
|
|
delta -= years * millisecondsPerYear;
|
|
|
|
const months = Math.floor(delta / millisecondsPerMonth);
|
|
delta -= months * millisecondsPerMonth;
|
|
|
|
const days = Math.floor(delta / millisecondsPerDay);
|
|
delta -= days * millisecondsPerDay;
|
|
|
|
const hours = Math.floor(delta / millisecondsPerHour);
|
|
delta -= hours * millisecondsPerHour;
|
|
|
|
const minutes = Math.floor(delta / millisecondsPerMinute);
|
|
delta -= minutes * millisecondsPerMinute;
|
|
|
|
const seconds = Math.floor(delta / millisecondsPerSecond);
|
|
delta -= seconds * millisecondsPerSecond;
|
|
|
|
const milliseconds = Math.floor(delta);
|
|
|
|
return {
|
|
years,
|
|
months,
|
|
days,
|
|
hours,
|
|
minutes,
|
|
seconds,
|
|
milliseconds
|
|
};
|
|
}
|