feat: add zoom scaling to timetable page #202

This commit is contained in:
SethBurkart123
2025-02-11 21:40:57 +11:00
parent 694d11477d
commit d65bfa8c46
2 changed files with 135 additions and 4 deletions
+125 -4
View File
@@ -753,6 +753,117 @@ async function LoadPageElements(): Promise<void> {
await handleSublink(sublink); await handleSublink(sublink);
} }
function handleTimetableZoom(): void {
console.log('Initializing timetable zoom controls');
// Lazy initialize state variables only when function is first called
let timetableZoomLevel = 1;
let baseContainerHeight: number | null = null;
const originalEntryPositions = new Map<Element, { topRatio: number; heightRatio: number }>();
// Create zoom controls
const zoomControls = document.createElement('div');
zoomControls.className = 'timetable-zoom-controls';
const zoomIn = document.createElement('button');
zoomIn.className = 'uiButton timetable-zoom iconFamily';
zoomIn.innerHTML = '&#xed93;'; // Using unicode for zoom in icon
const zoomOut = document.createElement('button');
zoomOut.className = 'uiButton timetable-zoom iconFamily';
zoomOut.innerHTML = '&#xed94;'; // Using unicode for zoom out icon
zoomControls.appendChild(zoomIn);
zoomControls.appendChild(zoomOut);
const toolbar = document.getElementById('toolbar');
toolbar?.appendChild(zoomControls);
const initializePositions = () => {
// Get the base container height from the first TD
const firstDayColumn = document.querySelector('.dailycal .content .days td') as HTMLElement;
if (!firstDayColumn) return false;
baseContainerHeight = parseInt(firstDayColumn.style.height) || firstDayColumn.offsetHeight;
// Store original ratios
const entries = document.querySelectorAll('.entriesWrapper .entry');
entries.forEach((entry: Element) => {
const entryEl = entry as HTMLElement;
// Calculate ratios relative to detected base height
if (baseContainerHeight === null) return;
const topRatio = parseInt(entryEl.style.top) / baseContainerHeight;
const heightRatio = parseInt(entryEl.style.height) / baseContainerHeight;
originalEntryPositions.set(entry, { topRatio, heightRatio });
});
return true;
};
const updateZoom = () => {
// Initialize positions if not already done
if (baseContainerHeight === null && !initializePositions()) {
console.error('Failed to initialize positions');
return;
}
console.debug(`Updating zoom level to: ${timetableZoomLevel}`);
// Calculate new container height
if (baseContainerHeight === null) return;
const newContainerHeight = baseContainerHeight * timetableZoomLevel;
// Update all day columns (TDs)
const dayColumns = document.querySelectorAll('.dailycal .content .days td');
dayColumns.forEach((td: Element) => {
(td as HTMLElement).style.height = `${newContainerHeight}px`;
});
// Update all entries using stored ratios
const entries = document.querySelectorAll('.entriesWrapper .entry');
entries.forEach((entry: Element) => {
const entryEl = entry as HTMLElement;
const originalRatios = originalEntryPositions.get(entry);
if (originalRatios) {
// Calculate new positions from original ratios
const newTop = originalRatios.topRatio * newContainerHeight;
const newHeight = originalRatios.heightRatio * newContainerHeight;
// Apply new values
entryEl.style.top = `${Math.round(newTop)}px`;
entryEl.style.height = `${Math.round(newHeight)}px`;
}
});
// Update time column to match
const timeColumn = document.querySelector('.times');
if (timeColumn) {
const times = timeColumn.querySelectorAll('.time');
const timeHeight = newContainerHeight / times.length;
times.forEach((time: Element) => {
(time as HTMLElement).style.height = `${timeHeight}px`;
});
}
};
zoomIn.addEventListener('click', () => {
if (timetableZoomLevel < 2) {
timetableZoomLevel += 0.2;
updateZoom();
}
});
zoomOut.addEventListener('click', () => {
if (timetableZoomLevel > 0.6) {
timetableZoomLevel -= 0.2;
updateZoom();
}
});
}
async function handleNotices(node: Element): Promise<void> { async function handleNotices(node: Element): Promise<void> {
if (!(node instanceof HTMLElement)) return; if (!(node instanceof HTMLElement)) return;
@@ -804,15 +915,25 @@ async function handleSublink(sublink: string | undefined): Promise<void> {
} }
async function handleTimetable(): Promise<void> { async function handleTimetable(): Promise<void> {
await waitForElm('.time', true, 10) await waitForElm('.time', true, 10);
// Store original heights when timetable loads
const lessons = document.querySelectorAll('.dailycal .lesson');
lessons.forEach((lesson: Element) => {
const lessonEl = lesson as HTMLElement;
lessonEl.setAttribute('data-original-height', lessonEl.offsetHeight.toString());
});
// Existing time format code
if (settingsState.timeFormat == '12') { if (settingsState.timeFormat == '12') {
const times = document.querySelectorAll('.timetablepage .times .time') const times = document.querySelectorAll('.timetablepage .times .time');
for (const time of times) { for (const time of times) {
if (!time.textContent) continue if (!time.textContent) continue;
time.textContent = convertTo12HourFormat(time.textContent, true) time.textContent = convertTo12HourFormat(time.textContent, true);
} }
} }
handleTimetableZoom();
} }
async function handleNewsPage(): Promise<void> { async function handleNewsPage(): Promise<void> {
+10
View File
@@ -11,10 +11,16 @@
--auto-background: var(--better-pale, var(--background-secondary)) !important; --auto-background: var(--better-pale, var(--background-secondary)) !important;
font-family: Rubik, sans-serif !important; font-family: Rubik, sans-serif !important;
} }
.hidden { .hidden {
display: none; display: none;
} }
button.uiButton.timetable-zoom.iconFamily,
.iconFamily {
font-family: "IconFamily" !important;
}
body, body,
.legacy-root input, .legacy-root input,
.legacy-root textarea, .legacy-root textarea,
@@ -229,6 +235,10 @@ html {
} }
} }
.timetable-zoom {
font-size: 14px !important;
}
#main > .dashboard { #main > .dashboard {
grid-template-columns: repeat(autofit, minmax(200px, 400px)) !important; grid-template-columns: repeat(autofit, minmax(200px, 400px)) !important;
background: unset; background: unset;