mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-25 17:26:15 -04:00
refactor(date-converter): improved ux and layout
This commit is contained in:
parent
5fa811a583
commit
fd9ab59172
7 changed files with 346 additions and 76 deletions
|
@ -1,44 +1,38 @@
|
|||
<template>
|
||||
<div>
|
||||
<n-card>
|
||||
<n-space justify="center">
|
||||
<n-form-item label="Use current date-time ?" label-placement="left" :show-feedback="false">
|
||||
<n-switch v-model:value="useCurrentDate" />
|
||||
</n-form-item>
|
||||
</n-space>
|
||||
<n-form-item
|
||||
:feedback="inputInvalid ? 'Invalid date for the current format' : ''"
|
||||
:validation-status="inputInvalid ? 'error' : undefined"
|
||||
>
|
||||
<n-input-group style="flex-grow: 1">
|
||||
<n-select
|
||||
v-model:value="inputFormat"
|
||||
style="width: 200px"
|
||||
:options="formats.map(({ name }, i) => ({ label: name, value: i }))"
|
||||
:disabled="useCurrentDate"
|
||||
/>
|
||||
<n-form-item :show-label="false" v-bind="validation.attrs">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
v-model:value="inputDate"
|
||||
:on-input="onDateInputChanged"
|
||||
placeholder="Put you date string here..."
|
||||
clearable
|
||||
:input-props="{ 'data-test-id': 'date-time-converter-input' }"
|
||||
/>
|
||||
|
||||
<n-input
|
||||
v-model:value="inputDate"
|
||||
:on-input="onDateInputChanged"
|
||||
:disabled="useCurrentDate"
|
||||
placeholder="Your date string..."
|
||||
/>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-divider style="margin-top: 0" />
|
||||
<div v-for="{ name, fromDate } in formats" :key="name" style="margin: 5px 0">
|
||||
<n-input-group>
|
||||
<n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label>
|
||||
<input-copyable :value="fromDate(baseDate)" />
|
||||
</n-input-group>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-select
|
||||
v-model:value="formatIndex"
|
||||
style="flex: 0 0 170px"
|
||||
:options="formats.map(({ name }, i) => ({ label: name, value: i }))"
|
||||
data-test-id="date-time-converter-format-select"
|
||||
/>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-divider style="margin-top: 0" />
|
||||
<div v-for="{ name, fromDate } in formats" :key="name" mt-1>
|
||||
<n-input-group>
|
||||
<n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label>
|
||||
<input-copyable
|
||||
:value="formatDateUsingFormatter(fromDate, normalizedDate)"
|
||||
placeholder="Invalid date..."
|
||||
:input-props="{ 'data-test-id': name }"
|
||||
/>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRafFn } from '@vueuse/core';
|
||||
import {
|
||||
formatISO,
|
||||
formatISO9075,
|
||||
|
@ -47,95 +41,132 @@ import {
|
|||
fromUnixTime,
|
||||
getTime,
|
||||
getUnixTime,
|
||||
isDate,
|
||||
parseISO,
|
||||
parseJSON,
|
||||
isDate,
|
||||
isValid,
|
||||
} from 'date-fns';
|
||||
import { ref } from 'vue';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import type { DateFormat, ToDateMapper } from './date-time-converter.types';
|
||||
import {
|
||||
isISO8601DateTimeString,
|
||||
isISO9075DateString,
|
||||
isRFC3339DateString,
|
||||
isRFC7231DateString,
|
||||
isTimestamp,
|
||||
isUTCDateString,
|
||||
isUnixTimestamp,
|
||||
isMongoObjectId,
|
||||
} from './date-time-converter.models';
|
||||
|
||||
const useCurrentDate = ref(true);
|
||||
const inputDate = ref('');
|
||||
const inputFormat = ref(6);
|
||||
const inputInvalid = ref(false);
|
||||
const baseDate = ref(new Date());
|
||||
|
||||
useRafFn(() => {
|
||||
if (useCurrentDate.value) {
|
||||
baseDate.value = new Date();
|
||||
const toDate: ToDateMapper = (date) => new Date(date);
|
||||
|
||||
function formatDateUsingFormatter(formatter: (date: Date) => string, date?: Date) {
|
||||
if (!date || !validation.isValid) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
function onDateInputChanged(value: string) {
|
||||
const { toDate } = formats[inputFormat.value];
|
||||
inputInvalid.value = false;
|
||||
|
||||
try {
|
||||
const formatted: Date | string = toDate(value);
|
||||
|
||||
if (!isDate(formatted) || isNaN(formatted.getTime())) {
|
||||
throw new Error('Invalid date');
|
||||
}
|
||||
|
||||
baseDate.value = formatted;
|
||||
} catch (_) {
|
||||
inputInvalid.value = true;
|
||||
}
|
||||
return withDefaultOnError(() => formatter(date), '');
|
||||
}
|
||||
|
||||
type Format = {
|
||||
name: string;
|
||||
fromDate: (date: Date) => string;
|
||||
toDate: (value: string) => Date;
|
||||
};
|
||||
|
||||
const toDate: Format['toDate'] = (date) => new Date(date);
|
||||
|
||||
const formats: Format[] = [
|
||||
const formats: DateFormat[] = [
|
||||
{
|
||||
name: 'JS locale date string',
|
||||
fromDate: (date) => date.toString(),
|
||||
toDate,
|
||||
formatMatcher: () => false,
|
||||
},
|
||||
{
|
||||
name: 'ISO 8601',
|
||||
fromDate: formatISO,
|
||||
toDate: parseISO,
|
||||
formatMatcher: (date) => isISO8601DateTimeString(date),
|
||||
},
|
||||
{
|
||||
name: 'ISO 9075',
|
||||
fromDate: formatISO9075,
|
||||
toDate: parseISO,
|
||||
formatMatcher: (date) => isISO9075DateString(date),
|
||||
},
|
||||
{
|
||||
name: 'RFC 3339',
|
||||
fromDate: formatRFC3339,
|
||||
toDate,
|
||||
formatMatcher: (date) => isRFC3339DateString(date),
|
||||
},
|
||||
{
|
||||
name: 'RFC 7231',
|
||||
fromDate: formatRFC7231,
|
||||
toDate,
|
||||
},
|
||||
{
|
||||
name: 'Timestamp',
|
||||
fromDate: (date) => String(getTime(date)),
|
||||
toDate: (ms) => parseJSON(+ms),
|
||||
formatMatcher: (date) => isRFC7231DateString(date),
|
||||
},
|
||||
{
|
||||
name: 'Unix timestamp',
|
||||
fromDate: (date) => String(getUnixTime(date)),
|
||||
toDate: (sec) => fromUnixTime(+sec),
|
||||
formatMatcher: (date) => isUnixTimestamp(date),
|
||||
},
|
||||
{
|
||||
name: 'Timestamp',
|
||||
fromDate: (date) => String(getTime(date)),
|
||||
toDate: (ms) => parseJSON(+ms),
|
||||
formatMatcher: (date) => isTimestamp(date),
|
||||
},
|
||||
{
|
||||
name: 'UTC format',
|
||||
fromDate: (date) => date.toUTCString(),
|
||||
toDate,
|
||||
formatMatcher: (date) => isUTCDateString(date),
|
||||
},
|
||||
{
|
||||
name: 'Mongo ObjectID',
|
||||
fromDate: (date) => Math.floor(date.getTime() / 1000).toString(16) + '0000000000000000',
|
||||
toDate: (objectId) => new Date(parseInt(objectId.substring(0, 8), 16) * 1000),
|
||||
formatMatcher: (date) => isMongoObjectId(date),
|
||||
},
|
||||
];
|
||||
|
||||
const formatIndex = ref(6);
|
||||
const now = useNow();
|
||||
|
||||
const normalizedDate = computed(() => {
|
||||
if (!inputDate.value) {
|
||||
return now.value;
|
||||
}
|
||||
|
||||
const { toDate } = formats[formatIndex.value];
|
||||
|
||||
try {
|
||||
return toDate(inputDate.value);
|
||||
} catch (_ignored) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
function onDateInputChanged(value: string) {
|
||||
const matchingIndex = formats.findIndex(({ formatMatcher }) => formatMatcher(value));
|
||||
if (matchingIndex !== -1) {
|
||||
formatIndex.value = matchingIndex;
|
||||
}
|
||||
}
|
||||
|
||||
const validation = useValidation({
|
||||
source: inputDate,
|
||||
watch: [formatIndex],
|
||||
rules: [
|
||||
{
|
||||
message: 'This date is invalid for this format',
|
||||
validator: (value) =>
|
||||
withDefaultOnError(() => {
|
||||
if (value === '') return true;
|
||||
|
||||
const maybeDate = formats[formatIndex.value].toDate(value);
|
||||
return isDate(maybeDate) && isValid(maybeDate);
|
||||
}, false),
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue