Files
api/web/composables/dateTime.ts
Eli Bosley 345e83bfb0 feat: upgrade nuxt-custom-elements (#1461)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added new modal dialogs and UI components, including activation steps,
OS update feedback, and expanded notification management.
* Introduced a plugin to configure internationalization, state
management, and Apollo client support in web components.
* Added a new Log Viewer page with a streamlined interface for viewing
logs.

* **Improvements**
* Centralized Pinia state management by consolidating all stores to use
a shared global Pinia instance.
* Simplified component templates by removing redundant
internationalization host wrappers.
* Enhanced ESLint configuration with stricter rules and global variable
declarations.
* Refined custom element build process to prevent jQuery conflicts and
optimize minification.
* Updated component imports and templates for consistent structure and
maintainability.
* Streamlined log viewer dropdowns using simplified select components
with improved formatting.
* Improved notification sidebar with filtering by importance and modular
components.
* Replaced legacy notification popups with new UI components and added
automatic root session creation for localhost requests.
* Updated OS version display and user profile UI with refined styling
and component usage.

* **Bug Fixes**
* Fixed component tag capitalization and improved type annotations
across components.

* **Chores**
* Updated development dependencies including ESLint plugins and build
tools.
* Removed deprecated log viewer patch class and cleaned up related test
fixtures.
  * Removed unused imports and simplified Apollo client setup.
* Cleaned up test mocks and removed obsolete i18n host component tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210730229632804

---------

Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
Co-authored-by: Zack Spear <zackspear@users.noreply.github.com>
2025-07-08 10:05:39 -04:00

234 lines
7.4 KiB
TypeScript

import { ref, computed, onBeforeMount, onBeforeUnmount } from 'vue';
import dayjs, { extend } from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import type { ComposerTranslation } from 'vue-i18n';
import type { DateFormatOption, ServerDateTimeFormat, TimeFormatOption } from '~/types/server';
/** @see https://day.js.org/docs/en/display/format#localized-formats */
extend(localizedFormat);
export interface TimeStringsObject {
years: number;
months: number;
days: number;
hours: number;
minutes: number;
seconds: number;
firstDateWasLater: boolean;
displaySeconds?: boolean;
}
const dateFormatOptions: DateFormatOption[] = [
{ format: '%c', display: 'ddd, D MMMM YYYY' }, // aka "system settings" in the date time settings of the webgui
{ format: '%A, %Y %B %e', display: 'ddd, YYYY MMMM D' },
{ format: '%A, %e %B %Y', display: 'ddd, D MMMM YYYY' },
{ format: '%A, %B %e, %Y', display: 'ddd, MMMM D, YYYY' },
{ format: '%A, %m/%d/%Y', display: 'ddd, MM/DD/YYYY' },
{ format: '%A, %d-%m-%Y', display: 'ddd, DD-MM-YYYY' },
{ format: '%A, %d.%m.%Y', display: 'ddd, DD.MM.YYYY' },
{ format: '%A, %Y-%m-%d', display: 'ddd, YYYY-MM-DD' },
];
const timeFormatOptions: TimeFormatOption[] = [
{ format: '%I:%M %p', display: 'hh:mma' },
{ format: '%R', display: 'HH:mm' },
];
/**
* the provided ref may not have a value until we get a response from the refreshServerState action
* So we need to watch for this value to be able to format it based on the user's date time preferences.
* @example below is how to use this composable
* const formattedRegExp = ref<any>();
* const setFormattedRegExp = () => { // ran in watch on regExp and onBeforeMount
* if (!regExp.value) { return; }
* const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
* formattedRegExp.value = outputDateTimeFormatted.value;
* };
* watch(regExp, (_newV) => {
* setFormattedRegExp();
* });
* onBeforeMount(() => {
* setFormattedRegExp();
* });
*
* @param format provided by Unraid server's state.php and set in the server store
* @param t translations
* @param hideMinutesSeconds true will hide minutes and seconds from the output
* @param providedDateTime optional provided date time to use instead of Date.now()
* @param diffCountUp true will count up from the provided date time instead of down
*/
const useDateTimeHelper = (
format: ServerDateTimeFormat | undefined,
t: ComposerTranslation,
hideMinutesSeconds?: boolean,
providedDateTime?: number | undefined,
diffCountUp?: boolean,
) => {
const buildStringFromValues = (payload: TimeStringsObject) => {
const {
years,
months,
days,
hours,
minutes,
seconds,
firstDateWasLater,
displaySeconds,
} = payload;
const result = [];
if (years) { result.push(t('year', years)); }
if (months) { result.push(t('month', months)); }
if (days) { result.push(t('day', days)); }
if (hours) { result.push(t('hour', hours)); }
if (minutes) { result.push(t('minute', minutes)); }
if (seconds && ((!years && !months && !days && !hours && !minutes) || displaySeconds)) { result.push(t('second', seconds)); }
if (firstDateWasLater) { result.push(t('ago')); }
return result.join(' ');
};
// use the format from the server to determine the format to use
const findMatchingFormat = (
selectedFormat: string,
formats: DateFormatOption[] | TimeFormatOption[],
): DateFormatOption | TimeFormatOption | undefined =>
formats.find(formatOption => formatOption.format === selectedFormat);
const dateFormat = findMatchingFormat(format?.date ?? dateFormatOptions[0].format, dateFormatOptions);
console.debug('[dateFormat]', dateFormat);
let displayFormat = `${dateFormat?.display}`;
console.debug('[displayFormat]', displayFormat);
if (!hideMinutesSeconds) {
const timeFormat = findMatchingFormat(format?.time ?? timeFormatOptions[0].format, timeFormatOptions);
displayFormat = `${displayFormat} ${timeFormat?.display}`;
console.debug('[displayFormat] with time', displayFormat);
}
const formatDate = (date: number): string =>
dayjs(date).format(displayFormat);
console.debug('[formatDate]', formatDate(Date.now()));
/**
* Original meat and potatos from:
* @version: 1.0.1
* @author: huangjinlin
* @repo: https://github.com/huangjinlin/dayjs-precise-range
*/
const buildValueObject = (
yDiff: number,
mDiff: number,
dDiff: number,
hourDiff: number,
minDiff: number,
secDiff: number,
firstDateWasLater: boolean
): TimeStringsObject => ({
years: yDiff,
months: mDiff,
days: dDiff,
hours: hourDiff,
minutes: minDiff,
seconds: secDiff,
firstDateWasLater,
});
const preciseDateDiff = (d1: dayjs.Dayjs, d2: dayjs.Dayjs): TimeStringsObject => {
let m1 = dayjs(d1);
let m2 = dayjs(d2);
let firstDateWasLater;
if (m1.isSame(m2)) {
return buildValueObject(0, 0, 0, 0, 0, 0, false);
}
if (m1.isAfter(m2)) {
const tmp = m1;
m1 = m2;
m2 = tmp;
firstDateWasLater = true;
} else {
firstDateWasLater = false;
}
let yDiff = m2.year() - m1.year();
let mDiff = m2.month() - m1.month();
let dDiff = m2.date() - m1.date();
let hourDiff = m2.hour() - m1.hour();
let minDiff = m2.minute() - m1.minute();
let secDiff = m2.second() - m1.second();
if (secDiff < 0) {
secDiff = 60 + secDiff;
minDiff -= 1;
}
if (minDiff < 0) {
minDiff = 60 + minDiff;
hourDiff -= 1;
}
if (hourDiff < 0) {
hourDiff = 24 + hourDiff;
dDiff -= 1;
}
if (dDiff < 0) {
const daysInLastFullMonth = dayjs(`${m2.year()}-${m2.month() + 1}`).subtract(1, 'M').daysInMonth();
if (daysInLastFullMonth < m1.date()) { // 31/01 -> 2/03
dDiff = daysInLastFullMonth + dDiff + (m1.date() - daysInLastFullMonth);
} else {
dDiff = daysInLastFullMonth + dDiff;
}
mDiff -= 1;
}
if (mDiff < 0) {
mDiff = 12 + mDiff;
yDiff -= 1;
}
return buildValueObject(yDiff, mDiff, dDiff, hourDiff, minDiff, secDiff, firstDateWasLater);
};
const readableDifference = (a = '', b = '') => {
try {
const x = a ? dayjs(parseInt(a, 10)) : dayjs();
const y = b ? dayjs(parseInt(b, 10)) : dayjs();
return preciseDateDiff(x, y);
} catch (error) {
throw new Error(`Couldn't calculate date difference with error: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
const dateDiff = (time: string, countUp: boolean) => countUp ? readableDifference(time, '') : readableDifference('', time);
// provide outputs for components
const outputDateTimeReadableDiff = ref<string>('');
const outputDateTimeFormatted = computed(() => formatDate(providedDateTime ?? Date.now()));
const runDiff = () => {
outputDateTimeReadableDiff.value = buildStringFromValues(dateDiff((providedDateTime ?? Date.now()).toString(), diffCountUp ?? false));
};
let interval: string | number | ReturnType<typeof setInterval> | undefined;
onBeforeMount(() => {
if (providedDateTime) {
runDiff();
interval = setInterval(() => {
runDiff();
}, 1000);
}
});
onBeforeUnmount(() => {
if (interval) {
clearInterval(interval);
}
});
return {
formatDate,
outputDateTimeReadableDiff,
outputDateTimeFormatted,
};
};
export default useDateTimeHelper;