mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-01 04:19:12 +00:00
refactor: changes in time input
This commit is contained in:
@@ -26,19 +26,17 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const [value, setValue] = useState<string>('');
|
const [value, setValue] = useState<string>('');
|
||||||
// avoid wrong submit on cancel
|
const ignoreChange = useRef(false);
|
||||||
let ignoreChange = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Resets input value to given
|
* @description Resets input value to given
|
||||||
*/
|
*/
|
||||||
const resetValue = useCallback(() => {
|
const resetValue = useCallback(() => {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line -- we use ignore change to stop submit on cancel
|
|
||||||
ignoreChange = true;
|
|
||||||
setValue(millisToString(time));
|
setValue(millisToString(time));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emitError(`Unable to parse date: ${error}`);
|
setValue(millisToString(0));
|
||||||
|
emitError(`Unable to parse time ${time}: ${error}`);
|
||||||
}
|
}
|
||||||
}, [emitError, time]);
|
}, [emitError, time]);
|
||||||
|
|
||||||
@@ -97,11 +95,6 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
*/
|
*/
|
||||||
const validateAndSubmit = useCallback(
|
const validateAndSubmit = useCallback(
|
||||||
(newValue: string) => {
|
(newValue: string) => {
|
||||||
if (ignoreChange) {
|
|
||||||
// eslint-disable-next-line -- we use this to prevent a wrong submit
|
|
||||||
ignoreChange = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const success = handleSubmit(newValue);
|
const success = handleSubmit(newValue);
|
||||||
if (success) {
|
if (success) {
|
||||||
const ms = forgivingStringToMillis(newValue);
|
const ms = forgivingStringToMillis(newValue);
|
||||||
@@ -111,7 +104,7 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
resetValue();
|
resetValue();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[delay, handleSubmit, resetValue],
|
[delay, handleSubmit, name, resetValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,6 +120,7 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
validateAndSubmit((event.target as HTMLInputElement).value);
|
validateAndSubmit((event.target as HTMLInputElement).value);
|
||||||
}
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
|
ignoreChange.current = true;
|
||||||
inputRef.current?.blur();
|
inputRef.current?.blur();
|
||||||
resetValue();
|
resetValue();
|
||||||
}
|
}
|
||||||
@@ -136,6 +130,10 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
|
|
||||||
const onBlurHandler = useCallback(
|
const onBlurHandler = useCallback(
|
||||||
(event: FocusEvent<HTMLInputElement>) => {
|
(event: FocusEvent<HTMLInputElement>) => {
|
||||||
|
if (ignoreChange.current) {
|
||||||
|
ignoreChange.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
validateAndSubmit((event.target as HTMLInputElement).value);
|
validateAndSubmit((event.target as HTMLInputElement).value);
|
||||||
},
|
},
|
||||||
[validateAndSubmit],
|
[validateAndSubmit],
|
||||||
|
|||||||
@@ -203,12 +203,6 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
{ value: '-0', expect: 0 },
|
{ value: '-0', expect: 0 },
|
||||||
{ value: '1', expect: 60 * 1000 },
|
{ value: '1', expect: 60 * 1000 },
|
||||||
{ value: '-1', expect: 60 * 1000 },
|
{ value: '-1', expect: 60 * 1000 },
|
||||||
{ value: '000000', expect: 0 },
|
|
||||||
{ value: '000001', expect: 1000 },
|
|
||||||
{ value: '000100', expect: 1000 * 60 },
|
|
||||||
{ value: '010000', expect: 1000 * 60 * 60 },
|
|
||||||
{ value: '230000', expect: 1000 * 60 * 60 * 23 },
|
|
||||||
{ value: '121212', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
|
||||||
{ value: '0h0m0s', expect: 0 },
|
{ value: '0h0m0s', expect: 0 },
|
||||||
{ value: '0h0m1s', expect: 1000 },
|
{ value: '0h0m1s', expect: 1000 },
|
||||||
{ value: '0h1m0s', expect: 1000 * 60 },
|
{ value: '0h1m0s', expect: 1000 * 60 },
|
||||||
@@ -225,10 +219,6 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
expect(typeof forgivingStringToMillis(s.value)).toBe('number');
|
expect(typeof forgivingStringToMillis(s.value)).toBe('number');
|
||||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
});
|
});
|
||||||
it(`handles ${s.value} to right`, () => {
|
|
||||||
expect(typeof forgivingStringToMillis(s.value, false)).toBe('number');
|
|
||||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -244,16 +234,113 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
it(`handles ${s.value} to the left`, () => {
|
it(`handles ${s.value} to the left`, () => {
|
||||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
});
|
});
|
||||||
it(`handles ${s.value} to the right`, () => {
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('it infers separators when non existent', () => {
|
||||||
|
const docs = [
|
||||||
|
{ value: '1', expect: 1000 * 60 }, // 00:01:00
|
||||||
|
{ value: '12', expect: 1000 * 60 * 12 }, // 00:12:00
|
||||||
|
{ value: '123', expect: 1000 * 60 * 23 + 1000 * 60 * 60 }, // 01:23:00
|
||||||
|
{ value: '1234', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 }, // 12:34:00
|
||||||
|
{ value: '12345', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 + 5 * 1000 }, // 12:34:05
|
||||||
|
{ value: '123456', expect: 1000 * 60 * 34 + 1000 * 60 * 60 * 12 + 56 * 1000 }, // 12:34:56
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const s of docs) {
|
||||||
|
it(`handles basic strings digits: ${s.value}`, () => {
|
||||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sixDigits = [
|
||||||
|
{ value: '000000', expect: 0 },
|
||||||
|
{ value: '000001', expect: 1000 },
|
||||||
|
{ value: '000100', expect: 1000 * 60 },
|
||||||
|
{ value: '010000', expect: 1000 * 60 * 60 },
|
||||||
|
{ value: '230000', expect: 1000 * 60 * 60 * 23 },
|
||||||
|
{ value: '121212', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const s of sixDigits) {
|
||||||
|
it(`handles string with 6 digits: ${s.value}`, () => {
|
||||||
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fiveDigits = [
|
||||||
|
{ value: '00000', expect: 0 },
|
||||||
|
{ value: '00001', expect: 1000 }, // 00:00:01
|
||||||
|
{ value: '00010', expect: 1000 * 60 }, // 00:01:00
|
||||||
|
{ value: '00100', expect: 1000 * 60 * 10 }, // 00:10:00
|
||||||
|
{ value: '01000', expect: 1000 * 60 * 60 }, // 01:00:00
|
||||||
|
{ value: '10000', expect: 1000 * 60 * 60 * 10 }, // 10:00:00
|
||||||
|
{ value: '23000', expect: 1000 * 60 * 60 * 23 }, // 23:00:00
|
||||||
|
{ value: '12121', expect: 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, // 12:12:01
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const s of fiveDigits) {
|
||||||
|
it(`handles string with 5 digits: ${s.value}`, () => {
|
||||||
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fourDigits = [
|
||||||
|
{ value: '0000', expect: 0 },
|
||||||
|
{ value: '0001', expect: 1000 * 60 }, // 00:01:00
|
||||||
|
{ value: '0010', expect: 1000 * 60 * 10 }, // 00:10:00
|
||||||
|
{ value: '0100', expect: 1000 * 60 * 60 }, // 01:00:00
|
||||||
|
{ value: '1000', expect: 1000 * 60 * 60 * 10 }, // 10:00:00
|
||||||
|
{ value: '2300', expect: 1000 * 60 * 60 * 23 }, // 23:00:00
|
||||||
|
{ value: '1212', expect: 12 * 60 * 1000 + 12 * 1000 * 60 * 60 }, // 12:12:00
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const s of fourDigits) {
|
||||||
|
it(`handles string with 4 digits: ${s.value}`, () => {
|
||||||
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const threeDigits = [
|
||||||
|
{ value: '000', expect: 0 },
|
||||||
|
{ value: '001', expect: 1000 * 60 }, // 00:01:00
|
||||||
|
{ value: '010', expect: 1000 * 60 * 10 }, // 00:10:00
|
||||||
|
{ value: '100', expect: 1000 * 60 * 60 }, // 01:00:00
|
||||||
|
{ value: '230', expect: 2 * 1000 * 60 * 60 + 30 * 1000 * 60 }, // 02:30:00
|
||||||
|
{ value: '121', expect: 21 * 60 * 1000 + 1000 * 60 * 60 }, // 01:21:00
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const s of threeDigits) {
|
||||||
|
it(`handles string with 3 digits: ${s.value}`, () => {
|
||||||
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const twoDigits = [
|
||||||
|
{ value: '00', expect: 0 },
|
||||||
|
{ value: '01', expect: 1000 * 60 }, // 00:01:00
|
||||||
|
{ value: '10', expect: 1000 * 60 * 10 }, // 00:10:00
|
||||||
|
{ value: '23', expect: 1000 * 60 * 23 }, // 00:23:00
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const s of twoDigits) {
|
||||||
|
it(`handles string with 2 digits: ${s.value}`, () => {
|
||||||
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleDigit = [...Array(10).keys()];
|
||||||
|
|
||||||
|
for (const s of singleDigit) {
|
||||||
|
it(`handles string with a single digits ${s}`, () => {
|
||||||
|
expect(forgivingStringToMillis(`${s}`)).toBe(s * 1000 * 60);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handles overflows', () => {
|
describe('handles overflows', () => {
|
||||||
const ts = [
|
const ts = [
|
||||||
// minutes overflow
|
// minutes overflow
|
||||||
{ value: '120', expect: 1000 * 60 * 120 },
|
|
||||||
{ value: '2.0.0', expect: 1000 * 60 * 120 },
|
{ value: '2.0.0', expect: 1000 * 60 * 120 },
|
||||||
{ value: '99', expect: 1000 * 60 * 99 },
|
{ value: '99', expect: 1000 * 60 * 99 },
|
||||||
{ value: '1.39.0', expect: 1000 * 60 * 99 },
|
{ value: '1.39.0', expect: 1000 * 60 * 99 },
|
||||||
@@ -272,57 +359,9 @@ describe('test forgivingStringToMillis()', () => {
|
|||||||
it(`handles ${s.value} to the left`, () => {
|
it(`handles ${s.value} to the left`, () => {
|
||||||
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
expect(forgivingStringToMillis(s.value)).toBe(s.expect);
|
||||||
});
|
});
|
||||||
it(`handles ${s.value} to the right`, () => {
|
|
||||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('test with fillRight (legacy)', () => {
|
|
||||||
describe('function handles separators', () => {
|
|
||||||
const testData = [
|
|
||||||
{ value: '1:2:3:10', expect: 3723000 },
|
|
||||||
{ value: '2,10', expect: 130000 },
|
|
||||||
{ value: '2.10', expect: 130000 },
|
|
||||||
{ value: '2 10', expect: 130000 },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const s of testData) {
|
|
||||||
it(`handles ${s.value}`, () => {
|
|
||||||
expect(typeof forgivingStringToMillis(s.value, false)).toBe('number');
|
|
||||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parses strings correctly', () => {
|
|
||||||
const ts = [
|
|
||||||
{ value: '1.2', expect: 60 * 1000 + 2 * 1000 },
|
|
||||||
{ value: '1.70', expect: 60 * 1000 + 70 * 1000 },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const s of ts) {
|
|
||||||
it(`handles ${s.value}`, () => {
|
|
||||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handles overflows', () => {
|
|
||||||
const ts = [
|
|
||||||
// minutes overflow
|
|
||||||
{ value: '0.120', expect: 120 * 1000 },
|
|
||||||
{ value: '0.99', expect: 99 * 1000 },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const s of ts) {
|
|
||||||
it(`handles ${s.value}`, () => {
|
|
||||||
expect(forgivingStringToMillis(s.value, false)).toBe(s.expect);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('test with fillLeft', () => {
|
describe('test with fillLeft', () => {
|
||||||
describe('function handles separators', () => {
|
describe('function handles separators', () => {
|
||||||
const testData = [
|
const testData = [
|
||||||
|
|||||||
@@ -83,17 +83,21 @@ const parse = (valueAsString: string): number => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Parses a time string to millis
|
* @description Parses a time string to millis, auto-filling to the left
|
||||||
* @param {string} value - time string
|
* @param {string} value - time string
|
||||||
* @param {boolean} fillLeft - autofill left = hours / right = seconds
|
|
||||||
* @returns {number} - time string in millis
|
* @returns {number} - time string in millis
|
||||||
*/
|
*/
|
||||||
export const forgivingStringToMillis = (value: string, fillLeft = true): number => {
|
export const forgivingStringToMillis = (value: string): number => {
|
||||||
let millis = 0;
|
let millis = 0;
|
||||||
|
|
||||||
const hours = parseInt(value.match(/(\d+)h/)?.[0] ?? 0, 10);
|
const hoursMatch = value.match(/(\d+)h/);
|
||||||
const minutes = parseInt(value.match(/(\d+)m/)?.[0] ?? 0, 10);
|
const hours = hoursMatch ? parse(hoursMatch[1]) : 0;
|
||||||
const seconds = parseInt(value.match(/(\d+)s/)?.[0] ?? 0, 10);
|
|
||||||
|
const minutesMatch = value.match(/(\d+)m/);
|
||||||
|
const minutes = minutesMatch ? parse(minutesMatch[1]) : 0;
|
||||||
|
|
||||||
|
const secondsMatch = value.match(/(\d+)s/);
|
||||||
|
const seconds = secondsMatch ? parse(secondsMatch[1]) : 0;
|
||||||
|
|
||||||
if (hours > 0 || minutes > 0 || seconds > 0) {
|
if (hours > 0 || minutes > 0 || seconds > 0) {
|
||||||
millis = hours * mth + minutes * mtm + seconds * mts;
|
millis = hours * mth + minutes * mtm + seconds * mts;
|
||||||
@@ -108,31 +112,32 @@ export const forgivingStringToMillis = (value: string, fillLeft = true): number
|
|||||||
millis += parse(second) * mtm;
|
millis += parse(second) * mtm;
|
||||||
millis += parse(third) * mts;
|
millis += parse(third) * mts;
|
||||||
} else if (first != null && second == null && third == null) {
|
} else if (first != null && second == null && third == null) {
|
||||||
// if string has one section,
|
// we only have one section, infer separators
|
||||||
// could be a complete string like 121010 - 12:10:10
|
|
||||||
if (first.length === 6) {
|
const length = first.length;
|
||||||
const hours = first.substring(0, 2);
|
if (length === 1) {
|
||||||
const minutes = first.substring(2, 4);
|
|
||||||
const seconds = first.substring(4);
|
|
||||||
millis = parse(hours) * mth;
|
|
||||||
millis += parse(minutes) * mtm;
|
|
||||||
millis += parse(seconds) * mts;
|
|
||||||
} else {
|
|
||||||
// otherwise lets treat as [minutes]
|
|
||||||
millis = parse(first) * mtm;
|
millis = parse(first) * mtm;
|
||||||
|
} else if (length === 2) {
|
||||||
|
millis = parse(first) * mtm;
|
||||||
|
} else if (length === 3) {
|
||||||
|
millis = parse(first[0]) * mth + parse(first.substring(1)) * mtm;
|
||||||
|
} else if (length === 4) {
|
||||||
|
millis = parse(first.substring(0, 2)) * mth + parse(first.substring(2)) * mtm;
|
||||||
|
} else if (length === 5) {
|
||||||
|
const hours = parse(first.substring(0, 2));
|
||||||
|
const minutes = parse(first.substring(2, 4));
|
||||||
|
const seconds = parse(first.substring(4));
|
||||||
|
millis = hours * mth + minutes * mtm + seconds * mts;
|
||||||
|
} else if (length >= 6) {
|
||||||
|
const hours = parse(first.substring(0, 2));
|
||||||
|
const minutes = parse(first.substring(2, 4));
|
||||||
|
const seconds = parse(first.substring(4));
|
||||||
|
millis = hours * mth + minutes * mtm + seconds * mts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first != null && second != null && third == null) {
|
if (first != null && second != null && third == null) {
|
||||||
// if string has two sections
|
millis = parse(first) * mth;
|
||||||
if (fillLeft) {
|
millis += parse(second) * mtm;
|
||||||
// treat as [hours] [minutes]
|
|
||||||
millis = parse(first) * mth;
|
|
||||||
millis += parse(second) * mtm;
|
|
||||||
} else {
|
|
||||||
// treat as [minutes] [seconds]
|
|
||||||
millis = parse(first) * mtm;
|
|
||||||
millis += parse(second) * mts;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user