93 lines
2.5 KiB
TypeScript
93 lines
2.5 KiB
TypeScript
import type { ClassValue } from "clsx";
|
|
import { clsx } from "clsx";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
export function debounce<Func extends (...args: any[]) => any>(func: Func, wait: number): Func {
|
|
let timeout: ReturnType<typeof setTimeout> | null;
|
|
return function (this: any, ...args: any[]) {
|
|
if (timeout) clearTimeout(timeout);
|
|
timeout = setTimeout(() => {
|
|
func.apply(this, args);
|
|
}, wait);
|
|
} as Func;
|
|
}
|
|
type AspectInfo = {
|
|
width: number;
|
|
height: number;
|
|
ratio: string; // ví dụ: "16:9"
|
|
float: number; // ví dụ: 1.777...
|
|
};
|
|
|
|
function gcd(a: number, b: number): number {
|
|
return b === 0 ? a : gcd(b, a % b);
|
|
}
|
|
|
|
export function getImageAspectRatio(url: string): Promise<AspectInfo> {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
|
|
img.onload = () => {
|
|
const w = img.naturalWidth;
|
|
const h = img.naturalHeight;
|
|
|
|
const g = gcd(w, h);
|
|
|
|
resolve({
|
|
width: w,
|
|
height: h,
|
|
ratio: `${w / g}:${h / g}`,
|
|
float: w / h
|
|
});
|
|
};
|
|
|
|
img.onerror = () => reject(new Error("Cannot load image"));
|
|
|
|
img.src = url;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
export const formatBytes = (bytes?: number) => {
|
|
if (!bytes) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
};
|
|
|
|
export const formatDuration = (seconds?: number) => {
|
|
if (!seconds) return '0:00';
|
|
const h = Math.floor(seconds / 3600);
|
|
const m = Math.floor((seconds % 3600) / 60);
|
|
const s = Math.floor(seconds % 60);
|
|
|
|
if (h > 0) {
|
|
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
}
|
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
};
|
|
|
|
export const formatDate = (dateString?: string) => {
|
|
if (!dateString) return '';
|
|
return new Date(dateString).toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
};
|
|
|
|
export const getStatusClass = (status?: string) => {
|
|
switch (status?.toLowerCase()) {
|
|
case 'ready': return 'bg-green-100 text-green-700';
|
|
case 'processing': return 'bg-yellow-100 text-yellow-700';
|
|
case 'failed': return 'bg-red-100 text-red-700';
|
|
default: return 'bg-gray-100 text-gray-700';
|
|
}
|
|
};
|