2024-03-11 11:37:58 +01:00
|
|
|
<div id="timeline">
|
|
|
|
<div class="inputs">
|
|
|
|
<input type="text" name="deb" id="deb" class="timepicker">
|
|
|
|
<input type="text" name="fin" id="fin" class="timepicker">
|
|
|
|
</div>
|
|
|
|
<div class="timeline-container">
|
|
|
|
<div class="period" style="left: 0%; width: 20%">
|
|
|
|
<div class="period-handle left"></div>
|
|
|
|
<div class="period-handle right"></div>
|
|
|
|
<div class="period-time">Time</div>
|
|
|
|
</div>
|
2023-04-17 15:44:55 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
|
|
|
|
|
|
|
|
|
|
const timelineContainer = document.querySelector(".timeline-container");
|
|
|
|
const periodTimeLine = document.querySelector(".period");
|
2023-05-17 20:46:10 +02:00
|
|
|
const t_start = {{ t_start }};
|
2024-05-23 09:17:56 +02:00
|
|
|
const t_mid = {{ t_mid }};
|
2023-05-17 20:46:10 +02:00
|
|
|
const t_end = {{ t_end }};
|
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
const tick_time = 60 / {{ tick_time }};
|
|
|
|
const tick_delay = 1 / tick_time;
|
2023-05-30 10:17:49 +02:00
|
|
|
|
2024-05-24 09:36:42 +02:00
|
|
|
const period_default = 2;
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
let handleMoving = false;
|
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
function createTicks() {
|
2023-06-02 17:19:55 +02:00
|
|
|
let i = t_start;
|
2023-04-25 22:59:06 +02:00
|
|
|
|
|
|
|
while (i <= t_end) {
|
2023-04-17 15:44:55 +02:00
|
|
|
const hourTick = document.createElement("div");
|
|
|
|
hourTick.classList.add("tick", "hour");
|
2023-04-25 22:59:06 +02:00
|
|
|
hourTick.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
|
2023-04-17 15:44:55 +02:00
|
|
|
timelineContainer.appendChild(hourTick);
|
|
|
|
|
|
|
|
const tickLabel = document.createElement("div");
|
|
|
|
tickLabel.classList.add("tick-label");
|
2023-04-25 22:59:06 +02:00
|
|
|
tickLabel.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
|
|
|
|
tickLabel.textContent = numberToTime(i);
|
2023-04-17 15:44:55 +02:00
|
|
|
timelineContainer.appendChild(tickLabel);
|
|
|
|
|
2023-04-25 22:59:06 +02:00
|
|
|
if (i < t_end) {
|
2023-06-02 17:19:55 +02:00
|
|
|
let j = Math.floor(i + 1);
|
2023-04-25 22:59:06 +02:00
|
|
|
|
|
|
|
while (i < j) {
|
2023-05-30 10:17:49 +02:00
|
|
|
i += tick_delay;
|
2023-04-25 22:59:06 +02:00
|
|
|
|
|
|
|
if (i <= t_end) {
|
|
|
|
const quarterTick = document.createElement("div");
|
|
|
|
quarterTick.classList.add("tick", "quarter");
|
|
|
|
quarterTick.style.left = `${computePercentage(i, t_start)}%`;
|
|
|
|
timelineContainer.appendChild(quarterTick);
|
|
|
|
}
|
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
2023-06-02 17:19:55 +02:00
|
|
|
i = j;
|
|
|
|
} else {
|
|
|
|
i++;
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-25 22:59:06 +02:00
|
|
|
function numberToTime(num) {
|
2023-06-02 17:19:55 +02:00
|
|
|
const integer = Math.floor(num);
|
2023-06-14 17:53:19 +02:00
|
|
|
const decimal = Math.round((num % 1) * 60);
|
2023-04-25 22:59:06 +02:00
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
let dec = `:${decimal}`;
|
2023-04-25 22:59:06 +02:00
|
|
|
if (decimal < 10) {
|
2023-06-02 17:19:55 +02:00
|
|
|
dec = `:0${decimal}`;
|
2023-04-25 22:59:06 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
let int = `${integer}`;
|
2023-04-25 22:59:06 +02:00
|
|
|
if (integer < 10) {
|
2023-06-02 17:19:55 +02:00
|
|
|
int = `0${integer}`;
|
2023-04-25 22:59:06 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
return int + dec;
|
2023-04-25 22:59:06 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
function snapToQuarter(value) {
|
2023-05-30 10:17:49 +02:00
|
|
|
|
|
|
|
|
|
|
|
return Math.round(value * tick_time) / tick_time;
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
|
|
|
|
2023-06-13 16:25:45 +02:00
|
|
|
function updatePeriodTimeLabel() {
|
|
|
|
const values = getPeriodValues();
|
|
|
|
const deb = numberToTime(values[0])
|
|
|
|
const fin = numberToTime(values[1])
|
|
|
|
const text = `${deb} - ${fin}`
|
|
|
|
periodTimeLine.querySelector('.period-time').textContent = text;
|
|
|
|
|
2024-03-11 11:37:58 +01:00
|
|
|
//Mise à jour des inputs
|
|
|
|
try{
|
|
|
|
$('#deb').val(deb);
|
|
|
|
$('#fin').val(fin);
|
|
|
|
}catch{}
|
|
|
|
|
2023-06-13 16:25:45 +02:00
|
|
|
}
|
|
|
|
|
2024-01-16 16:15:48 +01:00
|
|
|
function timelineMainEvent(event) {
|
2023-04-19 20:58:15 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
const startX = (event.clientX || event.changedTouches[0].clientX);
|
2023-04-19 20:58:15 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
if (event.target.classList.contains("period-handle")) {
|
|
|
|
const startWidth = parseFloat(periodTimeLine.style.width);
|
|
|
|
const startLeft = parseFloat(periodTimeLine.style.left);
|
|
|
|
const isLeftHandle = event.target.classList.contains("left");
|
|
|
|
handleMoving = true
|
|
|
|
const onMouseMove = (moveEvent) => {
|
2023-04-19 20:58:15 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
if (!handleMoving) return;
|
2023-06-13 16:25:45 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
|
|
|
|
const containerWidth = timelineContainer.clientWidth;
|
|
|
|
const newWidth =
|
|
|
|
startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100;
|
2023-04-19 20:58:15 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
if (isLeftHandle) {
|
|
|
|
const newLeft = startLeft + (deltaX / containerWidth) * 100;
|
|
|
|
adjustPeriodPosition(newLeft, newWidth);
|
|
|
|
} else {
|
|
|
|
adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth);
|
|
|
|
}
|
2023-04-19 20:58:15 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
updatePeriodTimeLabel();
|
|
|
|
};
|
|
|
|
const mouseUp = () => {
|
|
|
|
snapHandlesToQuarters();
|
|
|
|
timelineContainer.removeEventListener("mousemove", onMouseMove);
|
|
|
|
handleMoving = false;
|
|
|
|
func_call();
|
2023-04-19 20:58:15 +02:00
|
|
|
|
|
|
|
}
|
2023-09-11 10:45:21 +02:00
|
|
|
timelineContainer.addEventListener("mousemove", onMouseMove);
|
|
|
|
timelineContainer.addEventListener("touchmove", onMouseMove);
|
|
|
|
document.addEventListener(
|
|
|
|
"mouseup",
|
|
|
|
mouseUp,
|
2023-11-13 08:35:19 +01:00
|
|
|
{ once: true }
|
2023-09-11 10:45:21 +02:00
|
|
|
);
|
|
|
|
document.addEventListener(
|
|
|
|
"touchend",
|
|
|
|
mouseUp,
|
2023-11-13 08:35:19 +01:00
|
|
|
{ once: true }
|
2023-09-14 14:25:27 +02:00
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
);
|
|
|
|
} else if (event.target === periodTimeLine) {
|
|
|
|
|
|
|
|
const startLeft = parseFloat(periodTimeLine.style.left);
|
|
|
|
|
|
|
|
const onMouseMove = (moveEvent) => {
|
|
|
|
if (handleMoving) return;
|
|
|
|
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
|
|
|
|
const containerWidth = timelineContainer.clientWidth;
|
|
|
|
const newLeft = startLeft + (deltaX / containerWidth) * 100;
|
|
|
|
|
|
|
|
adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width));
|
|
|
|
|
|
|
|
updatePeriodTimeLabel();
|
|
|
|
};
|
|
|
|
const mouseUp = () => {
|
|
|
|
snapHandlesToQuarters();
|
|
|
|
timelineContainer.removeEventListener("mousemove", onMouseMove);
|
|
|
|
func_call();
|
|
|
|
}
|
|
|
|
timelineContainer.addEventListener("mousemove", onMouseMove);
|
|
|
|
timelineContainer.addEventListener("touchmove", onMouseMove);
|
|
|
|
document.addEventListener(
|
|
|
|
"mouseup",
|
|
|
|
mouseUp,
|
|
|
|
{ once: true }
|
|
|
|
);
|
|
|
|
document.addEventListener(
|
|
|
|
"touchend",
|
|
|
|
mouseUp,
|
|
|
|
{ once: true }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-16 16:15:48 +01:00
|
|
|
let func_call = () => { };
|
|
|
|
|
2023-09-11 10:45:21 +02:00
|
|
|
function setupTimeLine(callback) {
|
2024-01-16 16:15:48 +01:00
|
|
|
func_call = callback;
|
|
|
|
timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e) });
|
|
|
|
timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e) });
|
2024-03-11 11:37:58 +01:00
|
|
|
|
|
|
|
const updateFromInputs = ()=>{
|
|
|
|
let deb = $('#deb').val();
|
|
|
|
let fin = $('#fin').val();
|
|
|
|
if (deb != '' && fin != '') {
|
|
|
|
deb = fromTime(deb);
|
|
|
|
fin = fromTime(fin);
|
|
|
|
try {
|
|
|
|
setPeriodValues(deb, fin);
|
|
|
|
} catch {
|
|
|
|
setPeriodValues(...getPeriodValues());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#deb').data('TimePicker').options.change = updateFromInputs;
|
|
|
|
$('#fin').data('TimePicker').options.change = updateFromInputs;
|
|
|
|
|
|
|
|
updatePeriodTimeLabel();
|
2023-04-19 20:58:15 +02:00
|
|
|
}
|
2023-04-17 15:44:55 +02:00
|
|
|
|
|
|
|
function adjustPeriodPosition(newLeft, newWidth) {
|
|
|
|
|
|
|
|
const snappedLeft = snapToQuarter(newLeft);
|
|
|
|
const snappedWidth = snapToQuarter(newWidth);
|
|
|
|
const minLeft = 0;
|
|
|
|
const maxLeft = 100 - snappedWidth;
|
|
|
|
|
|
|
|
const clampedLeft = Math.min(Math.max(snappedLeft, minLeft), maxLeft);
|
|
|
|
|
|
|
|
periodTimeLine.style.left = `${clampedLeft}%`;
|
|
|
|
periodTimeLine.style.width = `${snappedWidth}%`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPeriodValues() {
|
|
|
|
const leftPercentage = parseFloat(periodTimeLine.style.left);
|
|
|
|
const widthPercentage = parseFloat(periodTimeLine.style.width);
|
|
|
|
|
2023-04-25 22:59:06 +02:00
|
|
|
const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start;
|
|
|
|
const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start;
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-05-30 10:17:49 +02:00
|
|
|
const startValue = snapToQuarter(startHour);
|
|
|
|
const endValue = snapToQuarter(endHour);
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)];
|
2023-04-25 22:59:06 +02:00
|
|
|
|
|
|
|
if (computedValues[0] > t_end || computedValues[1] < t_start) {
|
2024-03-11 11:37:58 +01:00
|
|
|
return [t_start, Math.min(t_end, t_start + period_default)];
|
2023-04-25 22:59:06 +02:00
|
|
|
}
|
|
|
|
|
2023-05-30 10:17:49 +02:00
|
|
|
if (computedValues[1] - computedValues[0] <= tick_delay && computedValues[1] < t_end - tick_delay) {
|
|
|
|
computedValues[1] += tick_delay;
|
2023-04-25 22:59:06 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
return computedValues;
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function setPeriodValues(deb, fin) {
|
2023-11-13 08:35:19 +01:00
|
|
|
if (fin < deb) {
|
|
|
|
throw new RangeError(`le paramètre 'deb' doit être inférieur au paramètre 'fin' ([${deb};${fin}])`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deb < 0 || fin < 0) {
|
|
|
|
throw new RangeError(`Les paramètres doivent être des entiers positifis ([${deb};${fin}])`)
|
|
|
|
}
|
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
deb = snapToQuarter(deb);
|
|
|
|
fin = snapToQuarter(fin);
|
|
|
|
let leftPercentage = (deb - t_start) / (t_end - t_start) * 100;
|
|
|
|
let widthPercentage = (fin - deb) / (t_end - t_start) * 100;
|
|
|
|
periodTimeLine.style.left = `${leftPercentage}%`;
|
|
|
|
periodTimeLine.style.width = `${widthPercentage}%`;
|
|
|
|
|
|
|
|
snapHandlesToQuarters();
|
2023-06-13 16:25:45 +02:00
|
|
|
updatePeriodTimeLabel()
|
2024-01-16 16:15:48 +01:00
|
|
|
func_call();
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function snapHandlesToQuarters() {
|
|
|
|
const periodValues = getPeriodValues();
|
2023-11-13 08:35:19 +01:00
|
|
|
let lef = Math.min(computePercentage(Math.abs(periodValues[0]), t_start), computePercentage(Math.abs(t_end), tick_delay));
|
2023-04-17 15:44:55 +02:00
|
|
|
if (lef < 0) {
|
|
|
|
lef = 0;
|
|
|
|
}
|
2023-06-02 17:19:55 +02:00
|
|
|
const left = `${lef}%`;
|
2023-04-25 22:59:06 +02:00
|
|
|
|
2023-11-13 08:35:19 +01:00
|
|
|
let wid = Math.max(computePercentage(Math.abs(periodValues[1]), Math.abs(periodValues[0])), computePercentage(tick_delay, 0));
|
2023-04-17 15:44:55 +02:00
|
|
|
if (wid > 100) {
|
|
|
|
wid = 100;
|
|
|
|
}
|
|
|
|
const width = `${wid}%`
|
|
|
|
periodTimeLine.style.left = left;
|
|
|
|
periodTimeLine.style.width = width;
|
2023-06-13 16:25:45 +02:00
|
|
|
|
|
|
|
updatePeriodTimeLabel()
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
|
|
|
|
2023-04-25 22:59:06 +02:00
|
|
|
function computePercentage(a, b) {
|
2023-06-02 17:19:55 +02:00
|
|
|
return ((a - b) / (t_end - t_start)) * 100;
|
2023-04-25 22:59:06 +02:00
|
|
|
}
|
2023-11-13 08:35:19 +01:00
|
|
|
function fromTime(time, separator = ":") {
|
|
|
|
const [hours, minutes] = time.split(separator).map((el) => Number(el))
|
|
|
|
return hours + minutes / 60
|
|
|
|
}
|
2023-04-25 22:59:06 +02:00
|
|
|
|
2024-03-11 11:37:58 +01:00
|
|
|
function getPeriodAsDate(){
|
|
|
|
let [deb, fin] = getPeriodValues();
|
|
|
|
deb = numberToTime(deb);
|
|
|
|
fin = numberToTime(fin);
|
|
|
|
|
|
|
|
const dateStr = $("#date")
|
|
|
|
.datepicker("getDate")
|
|
|
|
.format("yyyy-mm-dd")
|
|
|
|
.substring(0, 10);
|
|
|
|
|
|
|
|
return {
|
|
|
|
deb: new Date(`${dateStr}T${deb}`),
|
|
|
|
fin: new Date(`${dateStr}T${fin}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
createTicks();
|
2024-01-12 11:36:00 +01:00
|
|
|
|
2023-06-02 17:19:55 +02:00
|
|
|
setPeriodValues(t_start, t_start + period_default);
|
2023-04-17 15:44:55 +02:00
|
|
|
|
2023-11-13 08:35:19 +01:00
|
|
|
{% if heures %}
|
|
|
|
let [heure_deb, heure_fin] = [{{ heures | safe }}]
|
|
|
|
if (heure_deb != '' && heure_fin != '') {
|
|
|
|
heure_deb = fromTime(heure_deb);
|
|
|
|
heure_fin = fromTime(heure_fin);
|
|
|
|
setPeriodValues(heure_deb, heure_fin)
|
|
|
|
}
|
|
|
|
{% endif %}
|
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
</script>
|
|
|
|
<style>
|
2024-03-11 11:37:58 +01:00
|
|
|
|
|
|
|
#timeline {
|
|
|
|
display: flex;
|
|
|
|
justify-content: start;
|
|
|
|
}
|
|
|
|
|
|
|
|
.inputs {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: space-between;
|
|
|
|
gap: 5px;
|
|
|
|
margin-bottom: 10px;
|
|
|
|
width: 5em;
|
|
|
|
}
|
|
|
|
|
2023-04-17 15:44:55 +02:00
|
|
|
.timeline-container {
|
|
|
|
width: 75%;
|
2023-07-26 16:43:49 +02:00
|
|
|
margin-left: 25px;
|
2023-04-17 15:44:55 +02:00
|
|
|
background-color: white;
|
|
|
|
border-radius: 15px;
|
|
|
|
position: relative;
|
|
|
|
height: 40px;
|
2023-07-26 16:43:49 +02:00
|
|
|
margin-bottom: 25px;
|
2023-04-17 15:44:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ... */
|
|
|
|
.tick {
|
|
|
|
position: absolute;
|
|
|
|
bottom: 0;
|
|
|
|
width: 1px;
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
}
|
|
|
|
|
|
|
|
.tick.hour {
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tick.quarter {
|
|
|
|
height: 50%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.tick-label {
|
|
|
|
position: absolute;
|
|
|
|
bottom: 0;
|
|
|
|
font-size: 12px;
|
|
|
|
text-align: center;
|
|
|
|
transform: translateY(100%) translateX(-50%);
|
|
|
|
user-select: none;
|
|
|
|
-webkit-user-select: none;
|
|
|
|
-moz-user-select: none;
|
|
|
|
-ms-user-select: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.period {
|
|
|
|
position: absolute;
|
|
|
|
height: 100%;
|
2023-11-16 14:25:06 +01:00
|
|
|
background-color: var(--color-secondary);
|
2023-04-17 15:44:55 +02:00
|
|
|
border-radius: 15px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.period-handle {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
bottom: 0;
|
|
|
|
width: 8px;
|
|
|
|
border-radius: 0 4px 4px 0;
|
|
|
|
cursor: col-resize;
|
|
|
|
}
|
|
|
|
|
|
|
|
.period-handle.right {
|
|
|
|
right: 0;
|
|
|
|
border-radius: 4px 0 0 4px;
|
|
|
|
}
|
2023-06-13 16:25:45 +02:00
|
|
|
|
|
|
|
.period .period-time {
|
|
|
|
display: none;
|
|
|
|
position: absolute;
|
|
|
|
left: calc(50% - var(--w)/2 - 5px);
|
|
|
|
justify-content: center;
|
|
|
|
align-content: center;
|
|
|
|
top: calc(-60% - 10px);
|
|
|
|
--w: 10em;
|
|
|
|
width: var(--w);
|
|
|
|
}
|
|
|
|
|
|
|
|
.period:hover .period-time {
|
|
|
|
display: flex;
|
|
|
|
|
2023-11-16 14:25:06 +01:00
|
|
|
background-color: var(--color-secondary);
|
2023-06-13 16:25:45 +02:00
|
|
|
border-radius: 15px;
|
|
|
|
padding: 5px;
|
|
|
|
}
|
2023-04-17 15:44:55 +02:00
|
|
|
</style>
|