photoCalendar/photoCalendar.js
2024-06-16 22:17:40 +00:00

402 lines
12 KiB
JavaScript

class PhotoCalendar {
dragOptions = {
isDragging: false,
x: null,
y: null,
dragging: false,
};
options = {
calendarStartY: 690,
divideMonths: false,
fillStyle: "white",
img: null,
photoOffsetX: 0,
photoOffsetY: 0,
year: null,
centerMonths: false,
};
constructor() {
this.initDarkModeSwitch();
this.initControls();
this.onYearChange();
}
initDarkModeSwitch() {
function setDarkMode(darkModeSelected) {
if (darkModeSelected) {
document.documentElement.classList.add("darkMode");
} else {
document.documentElement.classList.remove("darkMode");
}
}
const darkModekCheckbox = document.getElementById("darkModeSwitch_checkbox");
darkModekCheckbox.checked = localStorage.getItem("darkMode") || false;
setDarkMode(darkModekCheckbox.checked);
darkModekCheckbox.addEventListener("change", (e) => {
localStorage.setItem("darkMode", e.target.checked);
setDarkMode(e.target.checked);
});
}
overPhotoImg(x, y) {
return y >= 0 && y <= this.options.calendarStartY;
}
getEventLocation(e) {
if (e.touches && e.touches.length == 1) {
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
} else if (e.clientX && e.clientY) {
return { x: e.clientX, y: e.clientY };
}
}
getMousePositionInCanvas(x, y) {
const canvas = this.getCanvas();
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const mouseX = (x - rect.left) * scaleX;
const mouseY = (y - rect.top) * scaleY;
return { x: mouseX, y: mouseY };
}
onPointerDown(e) {
let x = this.getEventLocation(e).x;
let y = this.getEventLocation(e).y;
const mousePosition = this.getMousePositionInCanvas(x, y);
if (this.overPhotoImg(mousePosition.x, mousePosition.y)) {
this.dragOptions.isDragging = true;
this.dragOptions.x = x - this.options.photoOffsetX;
this.dragOptions.y = y - this.options.photoOffsetY;
this.renderPhoto();
return;
}
this.dragStop();
this.dragOptions.x = x;
this.dragOptions.y = y;
}
onPointerMove(e) {
if (this.dragOptions.isDragging) {
// this.options.photoOffsetX = this.getEventLocation(e).x - this.dragOptions.x;
this.options.photoOffsetY = this.getEventLocation(e).y - this.dragOptions.y;
this.renderPhoto();
return;
}
// mouse over
let x = this.getEventLocation(e).x;
let y = this.getEventLocation(e).y;
}
onPointerUp(e) {
this.dragStop();
}
dragStop() {
this.dragOptions.isDragging = false;
}
handleTouch(e, singleTouchHandler) {
if (e.touches.length <= 1) {
singleTouchHandler(e);
if (e.type == "touchend") {
e.preventDefault();
this.dragStop();
return;
}
}
if (e.type == "touchmove" && e.touches.length == 2) {
this.dragStop();
}
}
initControls() {
const self = this;
this.options.year = new Date().getFullYear() + 1;
document.getElementById("year").value = this.options.year;
document.getElementById("year").addEventListener("change", (e) => {
self.options.year = e.target.value;
self.onYearChange();
});
document.getElementById("showMonthSeparators").addEventListener("click", (e) => {
self.options.divideMonths = e.target.checked;
self.onYearChange();
});
document.getElementById("centerMonths").addEventListener("click", (e) => {
self.options.centerMonths = e.target.checked;
self.onYearChange();
});
document.getElementById("imageInput").addEventListener("change", (e) => self.handleImageUpload(e));
document.getElementById("downloadButton").addEventListener("click", (e) => self.downloadCanvas());
// Handle photo position
const canvas = this.getCanvas();
canvas.addEventListener("mousedown", (e) => self.onPointerDown(e));
canvas.addEventListener("touchstart", (e) => self.handleTouch(e, (e) => self.onPointerDown(e)));
canvas.addEventListener("mouseup", (e) => self.onPointerUp(e));
canvas.addEventListener("touchend", (e) => self.handleTouch(e, (e) => self.onPointerUp(e)));
canvas.addEventListener("mousemove", (e) => self.onPointerMove(e));
canvas.addEventListener("touchmove", (e) => self.handleTouch(e, (e) => self.onPointerMove(e)));
}
handleImageUpload(event) {
const self = this;
const canvas = self.getCanvas();
const ctx = canvas.getContext("2d");
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = function (e) {
const img = new Image();
img.onload = function () {
self.options.img = img;
self.renderPhoto();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
onYearChange() {
const calendar = this.createCalendar(this.options.year);
this.renderCalendar(calendar);
}
createCalendar(year) {
const calendar = [];
const daysInWeek = 7;
const monthsInYear = 12;
// Helper function to determine the number of days in a given month and year
function getDaysInMonth(month, year) {
return new Date(year, month + 1, 0).getDate();
}
// Helper function to get the day of the week (0 is Monday, 6 is Sunday)
function getDayOfWeek(year, month, day) {
const date = new Date(year, month, day);
const dayOfWeek = date.getDay();
return (dayOfWeek + 6) % 7; // Adjust so Monday is 0 and Sunday is 6
}
for (let month = 0; month < monthsInYear; month++) {
const daysInMonth = getDaysInMonth(month, year);
const monthArray = [];
let weekArray = Array(daysInWeek).fill(null);
for (let day = 1; day <= daysInMonth; day++) {
const dayOfWeek = getDayOfWeek(year, month, day);
weekArray[dayOfWeek] = day;
if (dayOfWeek === 6 || day === daysInMonth) {
// End of the week or month
monthArray.push(weekArray);
weekArray = Array(daysInWeek).fill(null);
}
}
calendar.push(monthArray);
}
return calendar;
}
getCanvas() {
const canvas = document.getElementById("calendarCanvas");
return canvas;
}
renderPhoto() {
const canvas = this.getCanvas();
const ctx = canvas.getContext("2d");
ctx.fillStyle = this.options.fillStyle;
ctx.fillRect(0, 0, canvas.width, this.options.calendarStartY - 1); // Clear canvas
if (this.options.img == null) {
return;
}
// Calculate the aspect ratio and new height
const aspectRatio = this.options.img.width / this.options.img.height;
const newWidth = canvas.width;
const newHeight = newWidth / aspectRatio;
ctx.save();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, this.options.calendarStartY - 1);
ctx.clip();
ctx.drawImage(this.options.img, this.options.photoOffsetX, this.options.photoOffsetY, newWidth, newHeight);
ctx.restore();
}
downloadCanvas() {
const canvas = this.getCanvas();
const link = document.createElement("a");
link.href = canvas.toDataURL("image/png");
link.download = "calendar.png";
link.click();
}
renderCalendar(calendar) {
const canvas = this.getCanvas();
const ctx = canvas.getContext("2d");
ctx.fillStyle = this.options.fillStyle;
ctx.fillRect(0, 0, canvas.width, canvas.height);
this.renderPhoto();
ctx.fillStyle = this.options.fillStyle;
ctx.fillRect(0, this.options.calendarStartY, canvas.width, canvas.height);
const monthWidth = canvas.width / 3;
const monthHeigth = 30 + 30 * 8;
for (let i = 0; i < 12; i++) {
this.renderMonth(calendar, i, canvas, (i % 3) * monthWidth, parseInt(i / 3) * monthHeigth + this.options.calendarStartY, monthWidth);
}
}
drawText(ctx, text, x, y) {
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText(text, x, y);
}
drawTextCentered(ctx, text, x, y, cellWidth) {
const centerX = x + cellWidth / 2;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, centerX, y);
}
getMonthNames() {
return Array.from(document.querySelectorAll("[data-months] span")).map((x) => x.innerHTML);
}
getWeekdayNames() {
return Array.from(document.querySelectorAll("[data-weekdays] span")).map((x) => x.innerHTML);
}
renderMonth(calendar, month, canvas, x, y, w) {
const ctx = canvas.getContext("2d");
const monthNames = this.getMonthNames(); //["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"];
const dayInitials = this.getWeekdayNames(); //["L", "M", "X", "J", "V", "S", "D"];
const cellWidth = w / 8;
const cellHeight = 30;
const headerHeight = 30;
if (this.options.divideMonths) {
ctx.beginPath();
ctx.rect(x, y, w, 30 + 30 * 8);
ctx.stroke();
}
x += cellWidth / 2;
y += headerHeight;
// Clear the area where the month will be rendered
ctx.fillStyle = this.options.fillStyle;
ctx.fillRect(x, y, 7 * cellWidth, calendar[month].length * cellHeight + headerHeight * 2);
// Draw the month name
ctx.fillStyle = "black";
ctx.font = "bold 28px Arial";
if (this.options.centerMonths) {
this.drawTextCentered(ctx, monthNames[month], x, y, cellWidth * 7);
} else {
this.drawText(ctx, monthNames[month], x + cellWidth / 3, y);
}
// Draw the day initials header
ctx.font = "24px Arial";
for (let i = 0; i < dayInitials.length; i++) {
// ctx.fillText(dayInitials[i], x + i * cellWidth, y + headerHeight);
this.drawTextCentered(ctx, dayInitials[i], x + i * cellWidth, y + headerHeight, cellWidth);
}
// Draw the days of the month
ctx.font = "22px Arial";
const monthData = calendar[month];
for (let week = 0; week < monthData.length; week++) {
for (let day = 0; day < monthData[week].length; day++) {
const dayNumber = monthData[week][day];
const dayText = dayNumber !== null ? dayNumber : "";
// Set color to red for Saturday (5) and Sunday (6), else default color
if (day === 5 || day === 6) {
ctx.fillStyle = "red";
} else {
ctx.fillStyle = "black";
}
// ctx.fillText(dayText, x + day * cellWidth, y + headerHeight * 2 + week * cellHeight);
this.drawTextCentered(ctx, dayText, x + day * cellWidth, y + headerHeight * 2 + week * cellHeight, cellWidth);
}
}
}
}
function translate(lang, firstTime = false) {
const en = {
"Año:": "Year:",
"Dividir meses": "Split months",
"Centrar meses": "Center months",
"Seleccionar Foto": "Select image",
"Descargar foto-calendario": "Download photo-calendar",
"Buy me a coffe": "Buy me a coffe",
"Support my work": "Support my work",
L: "M",
M: "T",
X: "W",
J: "T",
V: "F",
S: "S",
D: "S",
Enero: "January",
Febrero: "February",
Marzo: "March",
Abril: "April",
Mayo: "May",
June: "June",
Julio: "July",
Agosto: "August",
Septiembre: "September",
Octubre: "October",
Noviembre: "November",
Diciembre: "December",
};
Array.from(document.querySelectorAll("[data-translate]")).forEach((x) => {
if (firstTime) {
x.dataset.translate = x.innerHTML;
}
if (en.hasOwnProperty(x.dataset.translate)) {
if (lang == "es") {
x.innerHTML = x.dataset.translate;
} else {
x.innerHTML = en[x.dataset.translate];
}
}
});
const languageSelector = document.getElementById("language");
languageSelector.value = lang;
}
document.addEventListener("DOMContentLoaded", () => {
const app = new PhotoCalendar();
const userLanguage = navigator.language || navigator.userLanguage;
const languageCode = userLanguage.split("-")[0];
if (languageCode != "es") {
console.log("Language Code", languageCode);
translate(languageCode, true);
app.onYearChange();
}
document.getElementById("language").addEventListener("change", (e) => {
translate(e.target.value);
app.onYearChange();
});
});