mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-03 17:55:21 +03:00
complete rework
This commit is contained in:
parent
cd39b0ca7a
commit
216194bfbd
@ -1,90 +1,170 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
import { ArrowDropdown } from '../icons/ArrowDropdown';
|
||||||
|
import { ArrowDropup } from '../icons/ArrowDropup';
|
||||||
|
|
||||||
const CalendarTimePicker = ({ dateRange, onChange }) => {
|
const TimePicker = ({ dateRange, onChange }) => {
|
||||||
const [selected, setSelected] = useState([0]);
|
const [error, setError] = useState(null);
|
||||||
|
const [timeRange, setTimeRange] = useState(new Set());
|
||||||
|
|
||||||
const timestampHour = (timestamp) => {
|
useEffect(() => {
|
||||||
if (!timestamp) return undefined;
|
if (!dateRange.after) return setTimeRange(new Set());
|
||||||
return new Date(timestamp).getHours();
|
}, [dateRange.after]);
|
||||||
|
|
||||||
|
// We subtract one from the before time to count the exact number of days in unix timestamp selected in the calendar
|
||||||
|
const before = dateRange.before ? new Date(dateRange.before) : new Date(new Date().setHours(24, 0, 0, 0));
|
||||||
|
const after = dateRange.after ? new Date(dateRange.after) : new Date(new Date().setHours(0, 0, 0, 0));
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
numberOfDaysSelected is a set that holds the number of days selected in the dateRange.
|
||||||
|
The loop iterates through the days starting from the after date's day to the before date's day.
|
||||||
|
If the before date's hour is 0, it skips it.
|
||||||
|
|
||||||
|
*/
|
||||||
|
const numberOfDaysSelected = new Set(
|
||||||
|
[...Array(before.getDate() - after.getDate() + 1)].map((_, i) => after.getDate() + i)
|
||||||
|
);
|
||||||
|
if (before.getHours() === 0) numberOfDaysSelected.delete(before.getDate());
|
||||||
|
|
||||||
|
// Create repeating array with the number of hours for each day selected ...23,24,0,1,2...
|
||||||
|
const hoursInDays = Array.from({ length: numberOfDaysSelected.size * 24 }, (_, i) => i % 24);
|
||||||
|
|
||||||
|
const handleTime = (hour) => {
|
||||||
|
if (!hour) return;
|
||||||
|
|
||||||
|
const _timeRange = new Set([...timeRange]);
|
||||||
|
_timeRange.add(hour);
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
if (timeRange.has(hour)) {
|
||||||
|
setTimeRange(new Set());
|
||||||
|
const resetBefore = before.setDate(after.getDate() + numberOfDaysSelected.size - 1);
|
||||||
|
return onChange({
|
||||||
|
after: after.setHours(0, 0, 0, 0) / 1000,
|
||||||
|
before: new Date(resetBefore).setHours(24, 0, 0, 0) / 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//update after
|
||||||
|
if (_timeRange.size === 1) {
|
||||||
|
// check if the first selected value is within first day
|
||||||
|
const selDay = Math.ceil(Math.min(..._timeRange) / 24);
|
||||||
|
if (selDay !== 1) {
|
||||||
|
return setError('Select a time on the initial day!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate days offset
|
||||||
|
const dayOffsetAfter = new Date(after).setHours(Math.min(..._timeRange));
|
||||||
|
|
||||||
|
let dayOffsetBefore = before;
|
||||||
|
if (numberOfDaysSelected.size === 1) {
|
||||||
|
dayOffsetBefore = new Date(after).setHours(Math.min(..._timeRange) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
after: dayOffsetAfter / 1000,
|
||||||
|
before: dayOffsetBefore / 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//update before
|
||||||
|
if (_timeRange.size > 1) {
|
||||||
|
let selectedDay = Math.ceil(Math.max(..._timeRange) / 24);
|
||||||
|
|
||||||
|
// if user selects time 00:00 for the next day, add one day
|
||||||
|
if (hour === 24 && selectedDay === numberOfDaysSelected.size - 1) {
|
||||||
|
selectedDay += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if end time is on the last day
|
||||||
|
if (selectedDay !== numberOfDaysSelected.size) {
|
||||||
|
return setError('Ending must occur on final day!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if end time is later than start time
|
||||||
|
const startHour = Math.min(..._timeRange);
|
||||||
|
if (hour <= startHour) {
|
||||||
|
return setError('Ending hour must be greater than start time!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all hours between start and end times to the set
|
||||||
|
for (let x = startHour; x <= hour; x++) {
|
||||||
|
_timeRange.add(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate days offset
|
||||||
|
const dayOffsetBefore = new Date(dateRange.after);
|
||||||
|
onChange({
|
||||||
|
after: dateRange.after / 1000,
|
||||||
|
// we add one hour to get full 60min of last selected hour
|
||||||
|
before: dayOffsetBefore.setHours(Math.max(..._timeRange) + 1) / 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < _timeRange.size; i++) {
|
||||||
|
setTimeRange((timeRange) => timeRange.add(Array.from(_timeRange)[i]));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTime = (number) => {
|
|
||||||
const currentHour = new Date();
|
|
||||||
currentHour.setHours(number, 0, 0, 0);
|
|
||||||
|
|
||||||
// Get current time if dateRange.after or dateRange.before are not set
|
|
||||||
const afterTime = dateRange.after ? dateRange.after : currentHour.getTime();
|
|
||||||
const beforetime = afterTime + 1;
|
|
||||||
|
|
||||||
// Update dateRange object with new "before" time and set selected to new number
|
|
||||||
if (selected.length === 1 && number > Math.max(...selected)) {
|
|
||||||
onChange({
|
|
||||||
after: afterTime / 1000,
|
|
||||||
before: new Date(afterTime).setHours(number + 1) / 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return setSelected([number]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating the start of the selected range
|
|
||||||
if (selected.length > 0 && number < selected[0]) {
|
|
||||||
onChange({
|
|
||||||
after: new Date(afterTime).setHours(number) / 1000,
|
|
||||||
before: dateRange.before / 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return setSelected([number]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating the end of the selected range
|
|
||||||
if (selected.length === 2) {
|
|
||||||
onChange({
|
|
||||||
after: dateRange.after ? dateRange.after / 1000 : 0,
|
|
||||||
before: new Date(beforetime) / 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return setSelected([number]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update dateRange object with both "after" and "before" times
|
|
||||||
if (selected.length === 1) {
|
|
||||||
onChange({
|
|
||||||
after: new Date(afterTime).setHours(number) / 1000,
|
|
||||||
before: new Date(afterTime).setHours(number + 1) / 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return setSelected([Math.min(selected[0], number), Math.max(selected[0], number)]);
|
|
||||||
}
|
|
||||||
setSelected([number]);
|
|
||||||
};
|
|
||||||
const isSelected = (after, before, idx) => {
|
const isSelected = (after, before, idx) => {
|
||||||
if (timestampHour(before) === 0 && timestampHour(after) === 0) return false;
|
return !!timeRange.has(idx);
|
||||||
|
|
||||||
return timestampHour(after) <= idx && timestampHour(before - 1) >= idx;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSelectedCss = 'bg-blue-600 transition-all duration-50 hover:rounded-none';
|
||||||
|
function randomGrayTone(shade) {
|
||||||
|
const grayTones = [
|
||||||
|
'bg-[#212529]/50',
|
||||||
|
'bg-[#343a40]/50',
|
||||||
|
'bg-[#495057]/50',
|
||||||
|
'bg-[#666463]/50',
|
||||||
|
'bg-[#817D7C]/50',
|
||||||
|
'bg-[#73706F]/50',
|
||||||
|
'bg-[#585655]/50',
|
||||||
|
'bg-[#4F4D4D]/50',
|
||||||
|
'bg-[#454343]/50',
|
||||||
|
'bg-[#363434]/50',
|
||||||
|
];
|
||||||
|
return grayTones[shade % grayTones.length];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div aria-label="Calendar timepicker, select a time range">
|
<>
|
||||||
<p className="block text-center font-medium mb-2 text-gray-300">TimePicker</p>
|
{error ? <span className="text-red-400 text-center text-xs absolute top-1 right-0 pr-2">{error}</span> : null}
|
||||||
<div className="w-24 px-1">
|
<div className="mt-2 pr-5 hidden xs:block" aria-label="Calendar timepicker, select a time range">
|
||||||
<div
|
<div className="flex items-center justify-center">
|
||||||
className="border border-gray-400 cursor-pointer hide-scroll"
|
<ArrowDropup className="w-10 text-center" />
|
||||||
style={{ maxHeight: '23rem', overflowY: 'scroll' }}
|
</div>
|
||||||
>
|
<div className="w-24 px-1">
|
||||||
{Array.from({ length: 24 }, (_, idx) => (
|
<div
|
||||||
<div
|
className="border border-gray-400/50 cursor-pointer hide-scroll shadow-md rounded-md"
|
||||||
key={idx}
|
style={{ maxHeight: '17rem', overflowY: 'scroll' }}
|
||||||
className={`w-full font-light border border-transparent hover:border-gray-300 rounded-sm text-center text-lg
|
>
|
||||||
${isSelected(dateRange.after, dateRange.before, idx) ? 'bg-blue-500' : ''}`}
|
{hoursInDays.map((_, idx) => (
|
||||||
onClick={() => handleTime(idx)}
|
<div
|
||||||
>
|
key={idx}
|
||||||
<span aria-label={`${idx}:00`}>{idx}:00</span>
|
className={`${isSelected(dateRange.after, dateRange.before, idx) ? isSelectedCss : ''}
|
||||||
</div>
|
${Math.min(...timeRange) === idx ? 'rounded-t-lg' : ''}
|
||||||
))}
|
${Math.max(...timeRange) === idx ? 'rounded-b-lg' : ''}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
text-gray-300 w-full font-light border border-transparent hover:border hover:rounded-md hover:border-gray-600 text-center text-sm
|
||||||
|
${randomGrayTone([Math.floor(idx / 24)])}`}
|
||||||
|
onClick={() => handleTime(idx)}
|
||||||
|
>
|
||||||
|
<span aria-label={`${idx}:00`}>{hoursInDays[idx]}:00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<ArrowDropdown className="w-10 text-center" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CalendarTimePicker;
|
export default TimePicker;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user