mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat: add zoom scaling to timetable page #202
This commit is contained in:
+125
-4
@@ -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 = ''; // Using unicode for zoom in icon
|
||||||
|
|
||||||
|
const zoomOut = document.createElement('button');
|
||||||
|
zoomOut.className = 'uiButton timetable-zoom iconFamily';
|
||||||
|
zoomOut.innerHTML = ''; // 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> {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user