Refactored entire rendering module; added new feature for "highlighting" a group, when the current date falls within that group

This commit is contained in:
Franco Speziali 2022-04-24 22:12:58 +02:00
parent 024abebf2a
commit da62052e79
9 changed files with 252 additions and 144 deletions

View File

@ -1,5 +1,7 @@
import { Event } from "../types";
import { DayGrouping } from "../EventGrouping";
import Renderer from "./Renderer";
import GroupDayRenderer from "./GroupRenderer/GroupDayRenderer";
export default class DayRenderer extends Renderer {
constructor(eventGrouping: DayGrouping) {
@ -7,21 +9,11 @@ export default class DayRenderer extends Renderer {
this.container.className += " scale-day";
}
protected renderDateAsIcon(eventDate, groupIndex): HTMLSpanElement {
const html = document.createElement("span");
html.className = "icon";
const dayInMonth = eventDate.getDate();
if (dayInMonth === 1) {
html.textContent = eventDate
.toLocaleDateString(undefined, { month: "long" })
.slice(0, 3);
html.className += " primary";
} else {
html.textContent = dayInMonth.toString();
}
return html;
protected renderGroups(
group: Event[],
groupDate: Date,
groupIndex: number
): HTMLDivElement {
return new GroupDayRenderer(group, groupDate, groupIndex).render();
}
}

View File

@ -0,0 +1,32 @@
import { Event } from "../../types";
import { DayGrouping } from "../../EventGrouping";
import GroupRenderer from "./GroupRenderer";
export default class GroupDayRenderer extends GroupRenderer {
constructor(group: Event[], groupDate: Date, groupIndex: number) {
super(group, groupDate, groupIndex);
if (DayGrouping.isToday(groupDate)) {
this.highlightGroup();
}
}
protected renderDateAsIcon(eventDate, groupIndex): HTMLSpanElement {
const html = document.createElement("span");
html.className = "icon";
const dayInMonth = eventDate.getDate();
if (dayInMonth === 1) {
html.textContent = eventDate
.toLocaleDateString(undefined, { month: "long" })
.slice(0, 3);
html.className += " primary";
} else {
html.textContent = dayInMonth.toString();
}
return html;
}
}

View File

@ -0,0 +1,37 @@
import { Event } from "../../types";
import { MonthGrouping } from "../../EventGrouping";
import GroupRenderer from "./GroupRenderer";
export default class GroupMonthRenderer extends GroupRenderer {
constructor(group: Event[], groupDate: Date, groupIndex: number) {
super(group, groupDate, groupIndex);
if (MonthGrouping.isThisMonth(groupDate)) {
this.highlightGroup();
}
}
protected renderDateAsIcon(eventDate, groupIndex): HTMLSpanElement {
const html = document.createElement("span");
html.className = "icon";
// unlike getDate(), getMonth is 0 based
const month = eventDate.getMonth() + 1;
if (month === 1) {
html.textContent = eventDate.toLocaleDateString(undefined, {
year: "numeric",
});
html.className += " primary";
} else {
html.textContent = eventDate
.toLocaleDateString(undefined, {
month: "long",
})
.slice(0, 3);
}
return html;
}
}

View File

@ -0,0 +1,108 @@
import { Event } from "../../types";
import { generateRandomColor } from "../../utilities";
import { format } from "date-fns";
export default abstract class GroupRenderer {
protected container: HTMLDivElement;
protected group: Event[];
protected groupDate: Date;
protected groupIndex: number;
protected abstract renderDateAsIcon(
eventDate: Date,
groupIndex: number
): HTMLSpanElement;
constructor(group: Event[], groupDate: Date, groupIndex: number) {
this.container = document.createElement("div");
this.container.className = "group";
this.group = group;
this.groupDate = groupDate;
this.groupIndex = groupIndex;
}
highlightGroup(): void {
this.container.className += " highlight";
}
render(): HTMLDivElement {
if (this.group.length) {
const htmlHoverCard = this.renderHoverCard(this.group);
this.container.appendChild(htmlHoverCard);
}
let htmlGroupIcon: HTMLSpanElement;
// only need to render icon from first event in group
const event = this.group[0];
if (event) {
htmlGroupIcon = this.renderIcon(event);
this.container.className += " icon";
if (event.bgColor) {
this.container.style.backgroundColor = event.bgColor;
} else {
const randomColor = generateRandomColor();
this.container.style.backgroundColor = `rgb(${randomColor.r}, ${randomColor.g}, ${randomColor.b})`;
}
} else {
htmlGroupIcon = this.renderDateAsIcon(this.groupDate, this.groupIndex);
}
this.container.appendChild(htmlGroupIcon);
return this.container;
}
renderIcon(event: Event | undefined): HTMLSpanElement {
const html = document.createElement("span");
html.className = "event";
if (event.icon) {
html.textContent = event.icon;
}
return html;
}
renderHoverCard(events: Event[]): HTMLDivElement {
const html = document.createElement("div");
html.className = "hover-card";
const htmlMonthYear = document.createElement("p");
htmlMonthYear.className = "month-year";
htmlMonthYear.textContent = events[0].date.toLocaleDateString(undefined, {
year: "numeric",
month: "long",
});
html.appendChild(htmlMonthYear);
const cardDetails = events.map((event) => {
if (!event) {
return;
}
const htmlEvent = document.createElement("div");
htmlEvent.className = "event";
const htmlEventDate = document.createElement("p");
htmlEventDate.className = "date";
htmlEventDate.textContent = format(event.date, "EEEE do");
const htmlEventTitle = document.createElement("p");
htmlEventTitle.className = "title";
htmlEventTitle.textContent = event.title;
const htmlEventText = document.createElement("p");
htmlEventText.className = "text";
htmlEventText.textContent = event.text;
htmlEvent.appendChild(htmlEventDate);
htmlEvent.appendChild(htmlEventTitle);
htmlEvent.appendChild(htmlEventText);
return htmlEvent;
});
cardDetails.forEach((cardDetail) => html.appendChild(cardDetail));
return html;
}
}

View File

@ -0,0 +1,36 @@
import { Event } from "../../types";
import { WeekGrouping } from "../../EventGrouping";
import GroupRenderer from "./GroupRenderer";
export default class GroupWeekRenderer extends GroupRenderer {
constructor(group: Event[], groupDate: Date, groupIndex: number) {
super(group, groupDate, groupIndex);
if (WeekGrouping.isThisWeek(groupDate)) {
this.highlightGroup();
}
}
protected renderDateAsIcon(eventDate, groupIndex): HTMLSpanElement {
const html = document.createElement("span");
html.className = "icon";
const week = WeekGrouping.getWeek(eventDate);
if (week === 1) {
html.textContent = eventDate.toLocaleDateString(undefined, {
year: "numeric",
});
html.className += " primary";
} else {
html.textContent = week.toString();
}
if (WeekGrouping.isThisWeek(eventDate)) {
html.className += " highlighted";
}
return html;
}
}

View File

@ -1,5 +1,7 @@
import { Event } from "../types";
import { MonthGrouping } from "../EventGrouping";
import Renderer from "./Renderer";
import GroupMonthRenderer from "./GroupRenderer/GroupMonthRenderer";
export default class MonthRenderer extends Renderer {
constructor(eventGrouping: MonthGrouping) {
@ -7,26 +9,13 @@ export default class MonthRenderer extends Renderer {
this.container.className += " scale-month";
}
protected renderDateAsIcon(eventDate, groupIndex): HTMLSpanElement {
const html = document.createElement("span");
html.className = "icon";
protected renderGroups(
group: Event[],
groupDate: Date,
groupIndex: number
): HTMLDivElement {
const monthGroup = new GroupMonthRenderer(group, groupDate, groupIndex);
// unlike getDate(), getMonth is 0 based
const month = eventDate.getMonth() + 1;
if (month === 1) {
html.textContent = eventDate.toLocaleDateString(undefined, {
year: "numeric",
});
html.className += " primary";
} else {
html.textContent = eventDate
.toLocaleDateString(undefined, {
month: "long",
})
.slice(0, 3);
}
return html;
return monthGroup.render();
}
}

View File

@ -1,13 +1,12 @@
import { format } from "date-fns";
import { generateRandomColor } from "../utilities";
import EventGrouping from "../EventGrouping/EventGrouping";
import { Event } from "../types";
export default abstract class Renderer {
protected abstract renderDateAsIcon(
eventDate: Date,
protected abstract renderGroups(
group: Event[],
groupDate: Date,
groupIndex: number
): HTMLSpanElement;
): HTMLDivElement;
protected container: HTMLDivElement;
protected eventGrouping: EventGrouping;
@ -18,95 +17,16 @@ export default abstract class Renderer {
this.eventGrouping = eventGrouping;
}
protected renderGroups(): HTMLDivElement[] {
return this.eventGrouping.groups.map((group, index) => {
const html = document.createElement("div");
html.className = "group";
protected _renderGroups(): HTMLDivElement[] {
return this.eventGrouping.groups.map((group: Event[], index: number) => {
const groupDate = this.eventGrouping.getDateFromGroupIndex(index);
if (group.length) {
const htmlHoverCard = this.renderHoverCard(group);
html.appendChild(htmlHoverCard);
}
let htmlGroupIcon: HTMLSpanElement;
// only need to render icon from first event in group
const event = group[0];
if (event) {
htmlGroupIcon = this.renderIcon(event);
html.className += " icon";
if (event.bgColor) {
html.style.backgroundColor = event.bgColor;
} else {
const randomColor = generateRandomColor();
html.style.backgroundColor = `rgb(${randomColor.r}, ${randomColor.g}, ${randomColor.b})`;
}
} else {
const eventDate = this.eventGrouping.getDateFromGroupIndex(index);
htmlGroupIcon = this.renderDateAsIcon(eventDate, index);
}
html.appendChild(htmlGroupIcon);
return html;
return this.renderGroups(group, groupDate, index);
});
}
protected renderHoverCard(events: Event[]): HTMLDivElement {
const html = document.createElement("div");
html.className = "hover-card";
const htmlMonthYear = document.createElement("p");
htmlMonthYear.className = "month-year";
htmlMonthYear.textContent = events[0].date.toLocaleDateString(undefined, {
year: "numeric",
month: "long",
});
html.appendChild(htmlMonthYear);
const cardDetails = events.map((event) => {
if (!event) {
return;
}
const htmlEvent = document.createElement("div");
htmlEvent.className = "event";
const htmlEventDate = document.createElement("p");
htmlEventDate.className = "date";
htmlEventDate.textContent = format(event.date, "EEEE do");
const htmlEventTitle = document.createElement("p");
htmlEventTitle.className = "title";
htmlEventTitle.textContent = event.title;
const htmlEventText = document.createElement("p");
htmlEventText.className = "text";
htmlEventText.textContent = event.text;
htmlEvent.appendChild(htmlEventDate);
htmlEvent.appendChild(htmlEventTitle);
htmlEvent.appendChild(htmlEventText);
return htmlEvent;
});
cardDetails.forEach((cardDetail) => html.appendChild(cardDetail));
return html;
}
protected renderIcon(event: Event | undefined): HTMLSpanElement {
const html = document.createElement("span");
html.className = "event";
if (event.icon) {
html.textContent = event.icon;
}
return html;
}
public render(): HTMLDivElement {
const groups = this.renderGroups();
const groups = this._renderGroups();
groups.forEach((group) => this.container.appendChild(group));

View File

@ -1,7 +1,7 @@
import { getWeek } from "date-fns";
import { Event } from "../types";
import { WeekGrouping } from "../EventGrouping";
import Renderer from "./Renderer";
import GroupWeekRenderer from "./GroupRenderer/GroupWeekRenderer";
export default class WeekRenderer extends Renderer {
constructor(eventGrouping: WeekGrouping) {
@ -9,21 +9,11 @@ export default class WeekRenderer extends Renderer {
this.container.className += " scale-week";
}
protected renderDateAsIcon(eventDate, groupIndex): HTMLSpanElement {
const html = document.createElement("span");
html.className = "icon";
const week = getWeek(eventDate);
if (week === 1) {
html.textContent = eventDate.toLocaleDateString(undefined, {
year: "numeric",
});
html.className += " primary";
} else {
html.textContent = week.toString();
}
return html;
protected renderGroups(
group: Event[],
groupDate: Date,
groupIndex: number
): HTMLDivElement {
return new GroupWeekRenderer(group, groupDate, groupIndex).render();
}
}

View File

@ -8,13 +8,17 @@
width: 36px;
height: 26px;
margin: 0 4px 4px 0;
border-top: 10px solid rgba(255, 140, 140, 0.5);
border-top: 10px solid rgba(255, 140, 140, 1);
border-right: 1px solid rgba(20, 20, 20, 0.7);
border-bottom: 1px solid rgba(20, 20, 20, 0.7);
border-left: 1px solid rgba(20, 20, 20, 0.2);
border-radius: 4px;
}
.event-calendar .group.highlight {
border-top: 10px solid rgba(255, 223, 65, 1);
}
.event-calendar .group .icon {
font-size: 15px;
text-align: center;