Files
2023-12-03 19:15:44 +01:00

526 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function customTimeline() {
let colors, titles, partTitles;
let partDurations = [];
let totalTime;
let tlContainer, tlContainerHeight;
let partBlockHeights = [];
let activePartIndex = 0;
let steps = {
tlElSteps: [],
scrollSteps: []
}
let previousScroll;
let grabbing = false;
let titresFrise;
let cursor = document.querySelector('#cursor');
let titresCursor;
let partRects;
let isScrollingFromGrab = false;
let prevPartIndex = 0;
let lastCallTimestamp = 0;
let timestampDebounce = 200;
// titres de parties dans le tableau
function setPartTitlesInPartition() {
let mainpartTitles = document.querySelectorAll('.isMainPart');
for (let mainpartTitle of mainpartTitles) {
if (mainpartTitle.nextElementSibling.classList.contains('isSubPart')) {
mainpartTitle.firstElementChild.firstElementChild.style.height = "1.5rem";
mainpartTitle.firstElementChild.style.marginBottom = "-0.25rem";
mainpartTitle.style.borderBottom = "0";
mainpartTitle.style.paddingBottom = "0";
mainpartTitle.nextElementSibling.style.paddingTop = "0";
}
}
}
// couleurs titres
function setPartsColors() {
colors = [
{'red' : 'cf0118'},
{'blue1': '0101c4'},
{'blue2': '01049e'},
{'blue3': '010678'},
{'blue4': '010952'},
{'blue5': '00113c'},
{'yellow1': 'ade719'},
{'yellow2': '8cc700'},
{'yellow3': '74af00'},
{'yellow4': '5c9900'},
{'yellow5': '377600'},
{'pink1': 'cf0118'},
{'pink2': 'a10418'}
];
titles = document.querySelectorAll('.isMainPart, .isSubPart');
partTitles = [];
for (let title of titles) {
if (!title.nextElementSibling.classList.contains('isSubPart')) {
partTitles.push(title);
}
}
for (let i = 0; i < partTitles.length; i++) {
if (partTitles[i].previousElementSibling?.classList.contains('isMainPart')) {
partTitles[i].previousElementSibling.firstElementChild.firstElementChild.style.backgroundColor = "#" + Object.values(colors[i])[0];
}
partTitles[i].firstElementChild.firstElementChild.style.backgroundColor = "#" + Object.values(colors[i])[0];
}
}
// set parts rectangles
function convertToSeconds(timeStr) {
let [hourStr, minStr] = timeStr.split('h');
if (!minStr) {
minStr = hourStr;
hourStr = '0';
}
minStr = minStr.replace('', ':');
minStr = minStr.replace("'", ':');
const [min, sec] = minStr.split(':');
return parseInt(hourStr) * 3600 + parseInt(min) * 60 + parseInt(sec);
}
function getPartsTimes() {
let partTimesNodes = document.querySelectorAll('body #app main tbody tr:not(.isContentPart) + tr:not(.isSubPart) td:first-of-type');
let partTimes = Array.from(partTimesNodes);
for (let i = 0; i < partTimes.length; i++) {
partTimes[i] = convertToSeconds(partTimes[i].innerText);
}
let lastTime = document.querySelectorAll('body #app main tbody tr:last-of-type td:first-of-type');
partTimes.push(convertToSeconds(lastTime[0].innerText));
for (let i = 0; i < partTimes.length - 1; i++) {
partDurations.push(partTimes[i + 1] - partTimes[i]);
}
totalTime = convertToSeconds(lastTime[0].innerText);
}
function getAllHeights() {
partBlockHeights = [];
tlContainer = document.querySelector('#timeline_container');
let header = document.querySelector('header');
let footer = document.querySelector('footer');
tlContainer.style.height = `calc(100vh - ${header.offsetHeight}px - ${footer.offsetHeight}px - 60px)`;
tlContainer.style.top = `${header.offsetHeight + 30}px`;
tlContainerHeight = tlContainer.offsetHeight;
for (let partDuration of partDurations) {
partBlockHeights.push(partDuration / totalTime * tlContainerHeight);
}
}
function drawFriseRects() {
for (let i = 0; i < partBlockHeights.length; i++) {
let partDiv = document.createElement('div');
partDiv.classList.add('tlRectPart');
partDiv.style.height = partBlockHeights[i] + "px";
partDiv.style.backgroundColor = "#" + Object.values(colors[i])[0];
tlContainer.prepend(partDiv);
tlContainer.children[0].addEventListener("mouseenter", function() {
let el = tlContainer.children[tlContainer.children.length - 1 - i];
if (Array.from(el.parentNode.children).length - Array.from(el.parentNode.children).indexOf(el) - 1 != activePartIndex) {
el.style.width = "32px";
toggleTitleHover(i, 'show');
}
});
tlContainer.children[0].addEventListener("mouseleave", function() {
let el = tlContainer.children[tlContainer.children.length - 1 - i];
if (Array.from(el.parentNode.children).length - Array.from(el.parentNode.children).indexOf(el) - 1 != activePartIndex) {
el.style.width = "22px";
}
toggleTitleHover(i, 'hide');
});
tlContainer.children[0].addEventListener("click", function() {
isScrollingFromGrab = false;
titresFrise[i].el.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
}
}
// detect if is scrolling
function isWindowScrolling() {
let currentScroll = window.scrollY;
if (currentScroll !== previousScroll) {
previousScroll = currentScroll;
return true;
} else {
return false;
}
}
// titres parties frise
function displayTimelineTitles() {
let mainWithoutSubAfter = [];
let mainWithSubAfter = [];
let subWithoutMainBefore = [];
let elements = Array.from(document.querySelectorAll('.isMainPart, .isSubPart'));
let lastMain = null;
for (let i = 0; i < elements.length; i++) {
let current = elements[i];
let next = elements[i + 1];
if (current.classList.contains('isMainPart')) {
lastMain = current;
if (next && next.classList.contains('isSubPart')) {
mainWithSubAfter.push({ index: i, main: current.innerText, sub: next.innerText, el: elements[i] });
} else {
mainWithoutSubAfter.push({ index: i, main: current.innerText, sub: "", el: elements[i] });
}
}
if (current.classList.contains('isSubPart')) {
let prevMain = lastMain && lastMain.classList.contains('isMainPart') ? lastMain.innerText : "";
subWithoutMainBefore.push({ index: i, main: prevMain, sub: current.innerText, el: elements[i] });
}
}
titresFrise = [...mainWithoutSubAfter, ...mainWithSubAfter, ...subWithoutMainBefore];
titresFrise.sort((a, b) => a.index - b.index);
for (let i = titresFrise.length - 1; i > 0; i--) {
let current = titresFrise[i];
let next = titresFrise[i - 1];
if (current.main === next.main && current.sub === next.sub) {
titresFrise.splice(i, 1);
}
}
let titreFriseEl = document.createElement('div');
titreFriseEl.setAttribute('id', 'titres_frise');
titreFriseEl.innerHTML = `
<p class="uppercase">${getCurrentTime(titresFrise[0].el)}</p>
<p>${titresFrise[0].main}</p>
<p class="font-authentic-60">${titresFrise[0].sub}</p>
`;
titreFriseEl.style.top = `${document.querySelector('#timeline_container').getBoundingClientRect().top}px`;
let main = document.querySelector('#main');
main.prepend(titreFriseEl);
titresCursor = document.querySelector('#titres_frise');
setTimeout(() => {
titreFriseEl.firstElementChild.innerText = getCurrentTime(titresFrise[0].el);
}, 10);
}
// création et togle des titres au survol des div de la timeline
function drawFixedTitles() {
let tlContainer = document.querySelector('#timeline_container');
let fixedTitlesContainer = document.createElement('div');
fixedTitlesContainer.setAttribute('id', 'fixedTitlesContainer');
main.prepend(fixedTitlesContainer);
for (let index = 0; index < titresFrise.length; index++) {
let titreFixedEl = document.createElement('div');
titreFixedEl.classList.add('tlFixedTitle');
titreFixedEl.style.top = `${tlContainer.children[index].getBoundingClientRect().top - 8}px`;
let titreFixedElContent = document.createElement('p');
titreFixedElContent.innerHTML = `
<p>${titresFrise[titresFrise.length - index - 1].main}</p>
<p class="font-authentic-60">${titresFrise[titresFrise.length - index - 1].sub}</p>
`;
titreFixedEl.append(titreFixedElContent);
fixedTitlesContainer.prepend(titreFixedEl);
}
}
function toggleTitleHover(elIndex, state) {
let fixedTitlesContainer = document.querySelector('#fixedTitlesContainer');
let el = fixedTitlesContainer.children[elIndex];
if (state === 'show') {
el.style.display = 'block';
setTimeout(() => {
el.style.opacity = '1';
}, 1);
} else if (state === 'hide') {
el.style.opacity = '0';
setTimeout(() => {
el.style.display = 'none';
}, 300);
}
}
// make the cursor move on scroll
function setupCursor() {
cursor.style.top = `${document.querySelector('#timeline_container').getBoundingClientRect().top}px`;
cursor.style.cursor = 'grab';
titresCursor.style.cursor = 'grab';
}
function setPartRects() {
partRects = document.querySelectorAll('#timeline_container div');
partRects = Array.from(partRects);
partRects = partRects.reverse();
partRects[0].style.width = '32px';
}
document.addEventListener("scroll", () => {
if (!grabbing) {
displayCurrentPartTitle(getCurrentPartFromScroll());
if (document.documentElement.scrollTop === 0) {
let firstTimeText = document.querySelector('tbody tr:nth-of-type(2) td:first-of-type');
titresCursor.firstElementChild.innerText = firstTimeText.innerText;
} else if (document.documentElement.scrollTop + window.innerHeight >= document.body.scrollHeight) {
let lastTimeText = document.querySelector('tbody tr:last-of-type td:first-of-type');
titresCursor.firstElementChild.innerText = lastTimeText.innerText;
}
moveCursorFromScroll(getCurrentPartFromScroll());
}
});
function makeElementDraggable(element, relatedEl) {
let offsetY;
element.addEventListener('mousedown', (e) => {
let elTransformY = element.style.transform ? +element.style.transform.split('(')[1].split('p')[0] : 0;
e.preventDefault();
grabbing = true;
offsetY = e.clientY - elTransformY;
element.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (grabbing) {
const y = e.clientY - offsetY;
if (e.clientY < tlContainerHeight + tlContainer.offsetTop && e.clientY > tlContainer.offsetTop && y > 0) {
element.style.transform = `translateY(${y}px)`;
relatedEl.style.transform = `translateY(${y}px)`;
if (!isNaN(y)) displayCurrentPartTitle(getCurrentPartFromCursor(y));
} else if (e.clientY < tlContainer.offsetTop || y <= 0) {
element.style.transform = `translateY(0px)`;
relatedEl.style.transform = `translateY(0px)`;
setTimeout(() => {
let firstTimeText = document.querySelector('tbody tr:nth-of-type(2) td:first-of-type');
titresCursor.firstElementChild.innerText = firstTimeText.innerText;
}, 100);
} else if (e.clientY >= tlContainerHeight + tlContainer.offsetTop) {
element.style.transform = `translateY(${tlContainerHeight}px)`;
relatedEl.style.transform = `translateY(${tlContainerHeight}px)`;
let lastTimeText = document.querySelector('tbody tr:last-of-type td:first-of-type');
titresCursor.firstElementChild.innerText = lastTimeText.innerText;
}
}
});
document.addEventListener('mouseup', (e) => {
if (grabbing) {
scrollOnGrab(element);
let elTransformY = element.style.transform ? +element.style.transform.split('(')[1].split('p')[0] : 0;
offsetY = e.clientY - elTransformY;
const y = e.clientY - offsetY;
if (e.clientY < tlContainer.offsetTop || y <= 0) {
window.scrollTo(0, 0);
} else {
setTimeout(() => {
titresCursor.firstElementChild.innerText = getCurrentTime(titresFrise[getCurrentPartFromCursor(y)]?.el);
}, 1000);
}
}
element.style.cursor = 'grab';
grabbing = false;
});
}
// get heights of parts dans le tableau et dans la timeline
function getSteps() {
steps.tlElSteps = [];
steps.scrollSteps = [];
for (let i = 0; i < partRects.length; i++) {
// steps.tlElSteps.push(partRects[i].offsetTop);
steps.tlElSteps.push(partRects[i].getBoundingClientRect().top);
}
let titles = document.querySelectorAll('.isMainPart, .isSubPart');
for (let title of titles) {
let nextLine = title.nextElementSibling;
if (!nextLine?.classList.contains('isSubPart')) {
// steps.scrollSteps.push(title.offsetTop);
steps.scrollSteps.push(title.offsetTop);
}
}
}
function scrollOnGrab(el) {
let scrollValue;
if (getCursorPositionInTimelinePart(el).stepAfterMouseUp === steps.scrollSteps.length - 1) {
scrollValue =
((document.documentElement.scrollHeight - steps.scrollSteps[getCursorPositionInTimelinePart(el).stepAfterMouseUp]) * getCursorPositionInTimelinePart(el).proportionInPart)
+ steps.scrollSteps[getCursorPositionInTimelinePart(el).stepAfterMouseUp];
} else {
scrollValue =
((steps.scrollSteps[getCursorPositionInTimelinePart(el).stepAfterMouseUp + 1] - steps.scrollSteps[getCursorPositionInTimelinePart(el).stepAfterMouseUp]) * getCursorPositionInTimelinePart(el).proportionInPart)
+ steps.scrollSteps[getCursorPositionInTimelinePart(el).stepAfterMouseUp];
}
isScrollingFromGrab = true;
window.scrollTo({ top: scrollValue, behavior: 'smooth' });
setTimeout(() => {
isScrollingFromGrab = false;
}, 1000);
}
function getCursorPositionInTimelinePart(el) {
let elTransformY;
if (!el.style.transform) {
elTransformY = 0;
} else {
elTransformY = +el.style.transform.split('(')[1].split('p')[0];
}
let tlPartHeight, tlPartBottom, tlPartTop, proportionInPart, stepAfterMouseUp;
tlPartHeight = partRects[getCurrentPartFromCursor(elTransformY) || 0].getBoundingClientRect().height;
tlPartBottom = partRects[getCurrentPartFromCursor(elTransformY) || 0].getBoundingClientRect().bottom - tlContainer.getBoundingClientRect().top;
tlPartTop = steps.tlElSteps[getCurrentPartFromCursor(elTransformY) || 0] - steps.tlElSteps[0];
proportionInPart = 1 - ((tlPartHeight - (elTransformY - tlPartTop)) / tlPartHeight);
if (proportionInPart > 0 && proportionInPart < 1) {
stepAfterMouseUp = getCurrentPartFromCursor(elTransformY);
return {stepAfterMouseUp, proportionInPart};
} else {
stepAfterMouseUp = getCurrentPartFromCursor(elTransformY);
proportionInPart = 0;
return {stepAfterMouseUp, proportionInPart};
}
}
function moveCursorFromScroll(currentPartIndex) {
if (!isScrollingFromGrab) {
let currentScroll = window.scrollY;
let tlPartHeight = parseInt(partRects[currentPartIndex].style.height);
let cursorTopValue = partRects[currentPartIndex].getBoundingClientRect().top - parseInt(tlContainer.style.top);
let currentScrollPartTop = steps.scrollSteps[currentPartIndex] || 0;
let currentScrollPartHeight;
if (steps.scrollSteps[currentPartIndex + 1]) {
currentScrollPartHeight = steps.scrollSteps[currentPartIndex + 1] - currentScrollPartTop;
} else {
currentScrollPartHeight = document.querySelector('body').scrollHeight - currentScrollPartTop;
}
let currentScrollSincePartBottom = currentScroll - currentScrollPartTop;
let scrollPartProportion = currentScrollSincePartBottom / currentScrollPartHeight;
cursorTopValue = cursorTopValue + tlPartHeight * scrollPartProportion;
if (cursorTopValue > 0) {
cursor.style.transform = `translateY(${cursorTopValue}px)`;
titresCursor.style.transform = `translateY(${cursorTopValue}px)`;
} else {
cursor.style.transform = `translateY(0px)`;
titresCursor.style.transform = `translateY(0px)`;
}
}
}
function displayCurrentPartTitle(currentPartIndex) {
if (isNaN(currentPartIndex)) currentPartIndex = 0;
const currentTime = performance.now();
if (currentTime - lastCallTimestamp >= timestampDebounce) {
lastCallTimestamp = currentTime;
if (titresCursor) titresCursor.firstElementChild.innerText = getCurrentTime(titresFrise[currentPartIndex]?.el); // ICI POUR METTRE LE TEMPS BIEN
}
let mainEl = titresCursor.children[1];
let subEl = titresCursor.lastElementChild;
if (mainEl.innerText != titresFrise[currentPartIndex]?.main || subEl.innerText != titresFrise[currentPartIndex]?.sub) {
mainEl.innerText = titresFrise[currentPartIndex]?.main;
subEl.innerText = titresFrise[currentPartIndex]?.sub;
partRects[prevPartIndex].style.width = '22px';
partRects[currentPartIndex].style.width = '32px';
}
activePartIndex = currentPartIndex;
prevPartIndex = currentPartIndex;
}
function getCurrentPartFromScroll() {
let currentScroll = window.scrollY;
for (let i = 0; i < steps.scrollSteps.length; i++) {
if (
(currentScroll + window.innerHeight / 2 >= steps.scrollSteps[i]
&& currentScroll + window.innerHeight / 2 <= steps.scrollSteps[i + 1]) ||
(currentScroll + window.innerHeight / 2 >= steps.scrollSteps[i]
&& i === steps.scrollSteps.length - 1)
) {
return(i);
}
}
}
function getCurrentPartFromCursor(cursorTransformY) {
for (let i = 0; i < steps.tlElSteps.length; i++) {
if (cursorTransformY >= steps.tlElSteps[i] - steps.tlElSteps[0] && cursorTransformY < steps.tlElSteps[i + 1] - steps.tlElSteps[0]) {
return(i);
} else if (cursorTransformY > steps.tlElSteps[steps.tlElSteps.length - 1] - steps.tlElSteps[0]) {
return(steps.tlElSteps.length - 1);
}
}
}
function getCurrentTime(titleEl) {
let nextRow = titleEl?.nextElementSibling;
let allRowsUnder = [];
if (!grabbing) {
while(nextRow) {
if (nextRow.offsetTop - window.innerHeight / 4 > window.scrollY &&
nextRow.classList.contains('isContentPart')) {
return nextRow.firstElementChild.innerText;
}
allRowsUnder.push(nextRow.firstElementChild.innerText);
nextRow = nextRow.nextElementSibling;
}
return allRowsUnder[allRowsUnder.length-1];
} else {
let cursor = document.querySelector('#cursor');
if (nextRow.classList.contains('isSubPart')) nextRow = nextRow.nextElementSibling.nextElementSibling;
while(nextRow) {
if (nextRow.classList.contains('isMainPart') || nextRow.classList.contains('isSubPart')) {
break;
}
allRowsUnder.push(nextRow.firstElementChild.innerText);
nextRow = nextRow.nextElementSibling;
}
let currentRowIndex = Math.floor(allRowsUnder.length * getCursorPositionInTimelinePart(cursor).proportionInPart);
return allRowsUnder[currentRowIndex];
}
}
setPartTitlesInPartition();
setPartsColors();
getPartsTimes();
setTimeout(() => {
getAllHeights();
drawFriseRects();
displayTimelineTitles();
setupCursor();
drawFixedTitles();
setPartRects();
makeElementDraggable(cursor, titresCursor);
makeElementDraggable(titresCursor, cursor);
getSteps();
}, 100);
let resizeTimeout;
window.addEventListener('resize', () => {
tlContainer.innerHTML = '';
document.querySelector("#titres_frise")?.remove();
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
getAllHeights();
drawFriseRects();
displayTimelineTitles();
setupCursor();
setPartRects();
makeElementDraggable(titresCursor, cursor);
getSteps();
}, 100);
});
}
export { customTimeline };