* add courses page * add api for courses * add course page * update course type * update client * update course page * refactor courses pages * render chapter content * shadow in course * fix video handler * update style * mark finished examples * fix media player * update locale * finish chapter * refactor * auto update chapter status * audo finish chapter * fix media provider * fix wavesurfer player * update continue btn * refactor chapters & page * minor fix * fix undefined * refactor * refactor * disable sentry in dev * clean markdown format before alignment * refactor * fix regenerate * fix transcription pre-process for `-` connector * upgrade deps * handle no chapters * add llm chat api * create llm chat * display llm message * create message * handle error * generate llm message * display llm datetime * scroll to message * tts for llm message * add course provider * refactor * translate llm message * fix llm chat introduction * refacotr * upgrade deps * refactor style * handle undefined * fix posts * update locales * update courses api * add enrollments count * upgrade yarn * upgrade deps * restore dep to fix package in mac * upgrade deps
141 lines
3.5 KiB
TypeScript
141 lines
3.5 KiB
TypeScript
import { type ClassValue, clsx } from "clsx";
|
|
import { twMerge } from "tailwind-merge";
|
|
import dayjs from "@renderer/lib/dayjs";
|
|
import { type DurationUnitType } from "dayjs/plugin/duration";
|
|
import i18next, { t } from "i18next";
|
|
import Chart from "chart.js/auto";
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
export function secondsToTimestamp(seconds: number) {
|
|
const h = Math.floor(seconds / 3600).toString();
|
|
const m = Math.floor((seconds % 3600) / 60).toString();
|
|
const s = Math.floor((seconds % 3600) % 60).toString();
|
|
|
|
return `${h.padStart(2, "0")}:${m.padStart(2, "0")}:${s.padStart(2, "0")}`;
|
|
}
|
|
|
|
export function humanizeDuration(
|
|
duration: number,
|
|
unit: DurationUnitType = "second"
|
|
) {
|
|
dayjs.locale(i18next.resolvedLanguage?.toLowerCase() || "en");
|
|
return dayjs.duration(duration, unit).humanize();
|
|
}
|
|
|
|
export function formatDuration(
|
|
duration: number,
|
|
unit: DurationUnitType = "second",
|
|
format = "HH:mm:ss"
|
|
) {
|
|
dayjs.locale(i18next.resolvedLanguage?.toLowerCase() || "en");
|
|
const display = dayjs.duration(duration, unit).format(format);
|
|
return display.replace(/^00:/, "");
|
|
}
|
|
|
|
export function bytesToSize(bytes: number) {
|
|
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
if (bytes === 0) {
|
|
return "0 Byte";
|
|
}
|
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
return Math.round(bytes / Math.pow(1024, i)) + " " + sizes[i];
|
|
}
|
|
|
|
export function formatDateTime(date: Date | string) {
|
|
dayjs.locale(i18next.resolvedLanguage?.toLowerCase() || "en");
|
|
const now = dayjs();
|
|
const then = dayjs(date);
|
|
|
|
if (now.diff(then, "hour") === 0) {
|
|
return then.fromNow();
|
|
} else if (now.isSame(then, "day")) {
|
|
return then.format("HH:mm");
|
|
} else if (now.diff(then, "year") === 0) {
|
|
return then.format("MM/DD HH:mm");
|
|
} else {
|
|
return then.format("YYYY/MM/DD HH:mm");
|
|
}
|
|
}
|
|
|
|
export function formatDate(date: string | Date) {
|
|
dayjs.locale(i18next.resolvedLanguage?.toLowerCase() || "en");
|
|
const now = dayjs();
|
|
const then = dayjs(date);
|
|
|
|
if (now.diff(then, "day") === 0) {
|
|
return t("today");
|
|
} else if (now.diff(then, "day") === 1) {
|
|
return t("yesterday");
|
|
} else {
|
|
return then.fromNow();
|
|
}
|
|
}
|
|
|
|
export function renderPitchContour(options: {
|
|
wrapper: HTMLElement;
|
|
canvasId: string;
|
|
labels: string[];
|
|
datasets: Chart["data"]["datasets"];
|
|
}) {
|
|
const { wrapper, datasets, labels, canvasId } = options;
|
|
|
|
const width = wrapper.getBoundingClientRect().width;
|
|
const height = wrapper.getBoundingClientRect().height;
|
|
const canvas = document.createElement("canvas");
|
|
canvas.id = canvasId;
|
|
canvas.style.position = "absolute";
|
|
canvas.style.width = `${width}px`;
|
|
canvas.style.height = `${height}px`;
|
|
canvas.style.top = "0";
|
|
canvas.style.left = "0";
|
|
|
|
wrapper.appendChild(canvas);
|
|
|
|
new Chart(canvas, {
|
|
type: "line",
|
|
data: {
|
|
labels,
|
|
datasets,
|
|
},
|
|
options: {
|
|
plugins: {
|
|
legend: {
|
|
display: false,
|
|
},
|
|
title: {
|
|
display: false,
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
autoSkip: false,
|
|
},
|
|
display: false,
|
|
grid: {
|
|
display: false,
|
|
},
|
|
border: {
|
|
display: false,
|
|
},
|
|
},
|
|
y: {
|
|
display: false,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export function imgErrorToDefalut(
|
|
e: React.SyntheticEvent<HTMLImageElement, Event>
|
|
) {
|
|
const target = e.target as HTMLImageElement;
|
|
target.onerror = null;
|
|
target.src = "assets/default-img.jpg";
|
|
}
|