feat: Background color fetching
All checks were successful
Remote Deploy / deploy (push) Successful in 6s
All checks were successful
Remote Deploy / deploy (push) Successful in 6s
This commit is contained in:
@@ -16,10 +16,94 @@ import fs from "fs";
|
||||
import parseAbsence from "../utils/parseAbsence.js"
|
||||
import parseTeachers from "../utils/parseTeachers.js"
|
||||
import ExcelJS from "exceljs"
|
||||
import JSZip from "jszip";
|
||||
import { parseStringPromise } from "xml2js";
|
||||
|
||||
/**
|
||||
* Read theme colors from the workbook
|
||||
*/
|
||||
async function getThemeColors(filePath) {
|
||||
const data = fs.readFileSync(filePath);
|
||||
const zip = await JSZip.loadAsync(data);
|
||||
|
||||
// list all files for debug
|
||||
|
||||
const themeFile = zip.file("xl/theme/theme1.xml");
|
||||
if (!themeFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const themeXml = await themeFile.async("text");
|
||||
const theme = await parseStringPromise(themeXml);
|
||||
const scheme = theme["a:theme"]?.["a:themeElements"]?.[0]?.["a:clrScheme"]?.[0];
|
||||
|
||||
if (!scheme) return null;
|
||||
|
||||
function getColor(node) {
|
||||
if (node["a:srgbClr"]) return node["a:srgbClr"][0].$.val;
|
||||
if (node["a:sysClr"]) return node["a:sysClr"][0].$.lastClr;
|
||||
return null;
|
||||
}
|
||||
|
||||
const colors = {
|
||||
0: getColor(scheme["a:dk1"]?.[0]),
|
||||
1: getColor(scheme["a:lt1"]?.[0]),
|
||||
2: getColor(scheme["a:dk2"]?.[0]),
|
||||
3: getColor(scheme["a:lt2"]?.[0]),
|
||||
4: getColor(scheme["a:accent1"]?.[0]),
|
||||
5: getColor(scheme["a:accent2"]?.[0]),
|
||||
6: getColor(scheme["a:accent3"]?.[0]),
|
||||
7: getColor(scheme["a:accent4"]?.[0]),
|
||||
8: getColor(scheme["a:accent5"]?.[0]),
|
||||
9: getColor(scheme["a:accent6"]?.[0]),
|
||||
};
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Excel tint to a base hex color
|
||||
*/
|
||||
function applyTintToHex(hex, tint = 0) {
|
||||
const r = parseInt(hex.slice(0, 2), 16);
|
||||
const g = parseInt(hex.slice(2, 4), 16);
|
||||
const b = parseInt(hex.slice(4, 6), 16);
|
||||
|
||||
const tintChannel = (c) =>
|
||||
tint > 0 ? Math.round(c + (255 - c) * tint) : Math.round(c * (1 + tint));
|
||||
|
||||
const nr = tintChannel(r);
|
||||
const ng = tintChannel(g);
|
||||
const nb = tintChannel(b);
|
||||
|
||||
return [nr, ng, nb]
|
||||
.map((v) => v.toString(16).padStart(2, "0"))
|
||||
.join("")
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve final hex for a cell fill
|
||||
*/
|
||||
function resolveCellColor(cell, themeColors) {
|
||||
if (!cell.fill?.fgColor) return null;
|
||||
|
||||
const fg = cell.fill.fgColor;
|
||||
|
||||
if (fg.argb) return `#${fg.argb}`;
|
||||
if (fg.theme !== undefined && themeColors) {
|
||||
const base = themeColors[fg.theme];
|
||||
if (!base) return null;
|
||||
return `#${applyTintToHex(base, fg.tint ?? 0)}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default async function parseV3(downloadedFilePath) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.readFile(downloadedFilePath);
|
||||
const themeColors = await getThemeColors(downloadedFilePath);
|
||||
|
||||
const teacherMap = await parseTeachers();
|
||||
|
||||
@@ -29,7 +113,7 @@ export default async function parseV3(downloadedFilePath) {
|
||||
const schedule = {};
|
||||
|
||||
for (const { dateKey, sheet } of resolvedDays) {
|
||||
const { changes, absence, inWork, takesPlace, reservedRooms } = extractDaySchedule(sheet, teacherMap);
|
||||
const { changes, absence, inWork, takesPlace, reservedRooms } = extractDaySchedule(sheet, teacherMap, themeColors);
|
||||
|
||||
schedule[dateKey] = {
|
||||
info: { inWork },
|
||||
@@ -104,9 +188,9 @@ function groupSheetsByDate(items) {
|
||||
// ────────────────────────────────────────────────────────────
|
||||
//
|
||||
|
||||
function extractDaySchedule(sheet, teacherMap) {
|
||||
function extractDaySchedule(sheet, teacherMap, themeColors) {
|
||||
return {
|
||||
changes: extractClassChanges(sheet),
|
||||
changes: extractClassChanges(sheet, themeColors),
|
||||
absence: extractAbsence(sheet, teacherMap),
|
||||
inWork: isPripravaSheet(sheet.name.toLowerCase()),
|
||||
takesPlace: extractTakesPlace(sheet),
|
||||
@@ -128,7 +212,7 @@ function isPripravaSheet(name) {
|
||||
// ────────────────────────────────────────────────────────────
|
||||
//
|
||||
|
||||
function extractClassChanges(sheet) {
|
||||
function extractClassChanges(sheet, themeColors) {
|
||||
const classRegex = /[AEC][0-4][a-c]?\s*\/.*/s;
|
||||
const prefixRegex = /[AEC][0-4][a-c]?/;
|
||||
|
||||
@@ -150,20 +234,20 @@ function extractClassChanges(sheet) {
|
||||
|
||||
classCells.forEach((address, index) => {
|
||||
const row = sheet.getRow(sheet.getCell(address).row);
|
||||
changes[classes[index]] = buildLessonArray(row, address);
|
||||
changes[classes[index]] = buildLessonArray(row, address, themeColors);
|
||||
});
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
function buildLessonArray(row, ignoreAddress) {
|
||||
function buildLessonArray(row, ignoreAddress, themeColors) {
|
||||
const lessons = [];
|
||||
|
||||
row.eachCell((cell) => {
|
||||
if (cell.address === ignoreAddress) return;
|
||||
|
||||
const colIndex = letterToNumber(cell.address.replace(/[0-9]/g, ""));
|
||||
lessons[colIndex] = parseLessonCell(cell);
|
||||
lessons[colIndex] = parseLessonCell(cell, themeColors);
|
||||
});
|
||||
|
||||
const normalized = Array.from(lessons, (x) => (x === undefined ? null : x));
|
||||
@@ -172,15 +256,15 @@ function buildLessonArray(row, ignoreAddress) {
|
||||
return normalized.slice(1, 11);
|
||||
}
|
||||
|
||||
function parseLessonCell(cell) {
|
||||
function parseLessonCell(cell, themeColors) {
|
||||
try {
|
||||
const text = (cell.text || "").trim();
|
||||
const cleanupRegex = /^úklid\s+(?:\d+\s+)?[A-Za-z]{2}$/;
|
||||
|
||||
if (!text || cleanupRegex.test(text) || !cell.fill?.fgColor) return null;
|
||||
|
||||
const backgroundColor = cell.fill.fgColor.argb === undefined ? undefined : `#${cell.fill.fgColor.argb}`;
|
||||
const foregroundColor = backgroundColor === undefined ? undefined : (
|
||||
const backgroundColor = resolveCellColor(cell, themeColors);
|
||||
const foregroundColor = !backgroundColor ? undefined : (
|
||||
cell.font?.color?.argb === undefined ? undefined : `#${cell.font.color.argb}`
|
||||
);
|
||||
|
||||
@@ -318,4 +402,4 @@ function formatNowTime() {
|
||||
);
|
||||
}
|
||||
|
||||
//parseV3("db/current.xlsx")
|
||||
parseV3("db/current.xlsx")
|
||||
|
||||
Reference in New Issue
Block a user