2023-12-21 11:25:25 +00:00
|
|
|
|
import { DEFAULT_RULES } from "src/constants";
|
2023-12-25 14:32:05 +00:00
|
|
|
|
import {
|
|
|
|
|
|
ContributionGraphConfig,
|
|
|
|
|
|
ContributionCellData,
|
|
|
|
|
|
CellStyleRule,
|
2024-01-10 01:51:08 +00:00
|
|
|
|
ContributionItem,
|
2023-12-25 14:32:05 +00:00
|
|
|
|
} from "src/types";
|
2023-12-21 11:25:25 +00:00
|
|
|
|
import { parseDate } from "src/util/dateUtils";
|
|
|
|
|
|
import {
|
|
|
|
|
|
generateByLatestDays,
|
|
|
|
|
|
generateByFixedDate,
|
2024-01-06 08:47:11 +00:00
|
|
|
|
generateByData,
|
2023-12-21 11:25:25 +00:00
|
|
|
|
} from "./matrixDataGenerator";
|
2023-12-25 14:32:05 +00:00
|
|
|
|
import { matchCellStyleRule } from "src/util/utils";
|
2023-12-30 00:02:07 +00:00
|
|
|
|
import { setTooltip } from "obsidian";
|
2024-01-10 02:48:42 +00:00
|
|
|
|
import { Locals } from "src/i18/messages";
|
2023-12-21 11:25:25 +00:00
|
|
|
|
|
|
|
|
|
|
export interface GraphRender {
|
|
|
|
|
|
render(container: HTMLElement, graphConfig: ContributionGraphConfig): void;
|
|
|
|
|
|
|
|
|
|
|
|
graphType(): string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export abstract class BaseGraphRender implements GraphRender {
|
2023-12-25 14:32:05 +00:00
|
|
|
|
constructor() {}
|
2023-12-21 11:25:25 +00:00
|
|
|
|
|
|
|
|
|
|
render(container: HTMLElement, graphConfig: ContributionGraphConfig): void {
|
|
|
|
|
|
throw new Error("Method not implemented.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
abstract graphType(): string;
|
|
|
|
|
|
|
2023-12-25 14:32:05 +00:00
|
|
|
|
renderTitle(
|
|
|
|
|
|
graphConfig: ContributionGraphConfig,
|
|
|
|
|
|
parent: HTMLElement
|
|
|
|
|
|
): HTMLElement {
|
2023-12-22 02:04:22 +00:00
|
|
|
|
const titleEl = document.createElement("div");
|
|
|
|
|
|
titleEl.className = "title";
|
|
|
|
|
|
if (graphConfig.title) {
|
|
|
|
|
|
titleEl.innerText = graphConfig.title;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (graphConfig.titleStyle) {
|
|
|
|
|
|
Object.assign(titleEl.style, graphConfig.titleStyle);
|
|
|
|
|
|
}
|
|
|
|
|
|
parent.appendChild(titleEl);
|
2023-12-25 14:32:05 +00:00
|
|
|
|
return titleEl;
|
2023-12-22 02:04:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-21 11:25:25 +00:00
|
|
|
|
renderCellRuleIndicator(
|
|
|
|
|
|
graphConfig: ContributionGraphConfig,
|
|
|
|
|
|
parent: HTMLElement
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (graphConfig.showCellRuleIndicators === false) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cellRuleIndicatorContainer = createDiv({
|
|
|
|
|
|
cls: "cell-rule-indicator-container",
|
|
|
|
|
|
parent: parent,
|
|
|
|
|
|
});
|
|
|
|
|
|
const cellRules = this.getCellRules(graphConfig);
|
|
|
|
|
|
createDiv({
|
|
|
|
|
|
cls: "cell text",
|
|
|
|
|
|
text: "less",
|
|
|
|
|
|
parent: cellRuleIndicatorContainer,
|
|
|
|
|
|
});
|
|
|
|
|
|
cellRules
|
|
|
|
|
|
.sort((a, b) => a.min - b.min)
|
|
|
|
|
|
.forEach((rule) => {
|
|
|
|
|
|
const cellEl = createDiv({
|
|
|
|
|
|
cls: ["cell"],
|
|
|
|
|
|
parent: cellRuleIndicatorContainer,
|
|
|
|
|
|
});
|
|
|
|
|
|
cellEl.className = "cell";
|
|
|
|
|
|
cellEl.style.backgroundColor = rule.color;
|
|
|
|
|
|
cellEl.innerText = rule.text || "";
|
|
|
|
|
|
|
|
|
|
|
|
// bind tips event
|
2023-12-30 00:02:07 +00:00
|
|
|
|
const summary = `${rule.min} ≤ contributions < ${rule.max}`;
|
|
|
|
|
|
setTooltip(cellEl, summary);
|
2023-12-21 11:25:25 +00:00
|
|
|
|
});
|
|
|
|
|
|
createDiv({
|
|
|
|
|
|
cls: "cell text",
|
|
|
|
|
|
text: "more",
|
|
|
|
|
|
parent: cellRuleIndicatorContainer,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-10 01:51:08 +00:00
|
|
|
|
renderActivityContainer(
|
|
|
|
|
|
graphConfig: ContributionGraphConfig,
|
|
|
|
|
|
parent: HTMLElement
|
|
|
|
|
|
): HTMLElement {
|
|
|
|
|
|
const activityContainer = createDiv({
|
|
|
|
|
|
cls: "activity-container",
|
|
|
|
|
|
parent: parent,
|
|
|
|
|
|
});
|
|
|
|
|
|
return activityContainer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
renderActivity(
|
|
|
|
|
|
graphConfig: ContributionGraphConfig,
|
|
|
|
|
|
cellData: ContributionCellData,
|
|
|
|
|
|
contaienr: HTMLElement
|
|
|
|
|
|
) {
|
|
|
|
|
|
contaienr.empty();
|
|
|
|
|
|
|
2024-01-15 01:59:15 +00:00
|
|
|
|
const closeButton = createEl("button", {
|
|
|
|
|
|
cls: "close-button",
|
|
|
|
|
|
text: 'x',
|
|
|
|
|
|
parent: contaienr,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
closeButton.onclick = () => {
|
|
|
|
|
|
contaienr.empty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-10 01:51:08 +00:00
|
|
|
|
let summary;
|
|
|
|
|
|
if (cellData.value > 0) {
|
2024-01-10 02:48:42 +00:00
|
|
|
|
summary = Locals.get().you_have_contributed_to
|
2024-01-10 01:51:08 +00:00
|
|
|
|
.replace("{date}", cellData.date)
|
|
|
|
|
|
.replace("{value}", cellData.value.toString());
|
|
|
|
|
|
} else {
|
2024-01-10 02:48:42 +00:00
|
|
|
|
summary = Locals.get().you_have_no_contributions_on
|
2024-01-10 01:51:08 +00:00
|
|
|
|
.replace("{date}", cellData.date)
|
|
|
|
|
|
.replace("{value}", "0");
|
|
|
|
|
|
}
|
|
|
|
|
|
createDiv({
|
|
|
|
|
|
cls: "activity-summary",
|
|
|
|
|
|
parent: contaienr,
|
|
|
|
|
|
text: summary,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const content = createDiv({
|
|
|
|
|
|
cls: "activity-content",
|
|
|
|
|
|
parent: contaienr,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// list-main
|
|
|
|
|
|
const list = createDiv({
|
|
|
|
|
|
cls: "activity-list",
|
|
|
|
|
|
parent: content,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// show top 10 items
|
|
|
|
|
|
const size = 10;
|
|
|
|
|
|
const items = cellData.items || [];
|
|
|
|
|
|
renderActivityItem(items.slice(0, size), list);
|
|
|
|
|
|
|
|
|
|
|
|
const navigation = createDiv({
|
|
|
|
|
|
cls: "activity-navigation",
|
|
|
|
|
|
parent: content,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let page = 1;
|
|
|
|
|
|
if (items.length > size) {
|
|
|
|
|
|
const loadMore = createEl("a", {
|
2024-01-10 02:48:42 +00:00
|
|
|
|
text: Locals.get().click_to_load_more,
|
2024-01-10 01:51:08 +00:00
|
|
|
|
href: "#",
|
|
|
|
|
|
parent: navigation,
|
|
|
|
|
|
});
|
|
|
|
|
|
loadMore.onclick = (event) => {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
page++;
|
|
|
|
|
|
renderActivityItem(
|
|
|
|
|
|
items.slice((page - 1) * size, page * size),
|
|
|
|
|
|
list
|
|
|
|
|
|
);
|
|
|
|
|
|
if (page * size >= items.length) {
|
|
|
|
|
|
loadMore.remove();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-21 11:25:25 +00:00
|
|
|
|
generateContributionData(graphConfig: ContributionGraphConfig) {
|
|
|
|
|
|
if (graphConfig.days) {
|
2024-01-10 01:51:08 +00:00
|
|
|
|
return generateByLatestDays(graphConfig.days, graphConfig.data);
|
2023-12-21 11:25:25 +00:00
|
|
|
|
} else if (graphConfig.fromDate && graphConfig.toDate) {
|
|
|
|
|
|
const fromDate = parseDate(graphConfig.fromDate);
|
|
|
|
|
|
const toDate = parseDate(graphConfig.toDate);
|
2024-01-10 01:51:08 +00:00
|
|
|
|
return generateByFixedDate(fromDate, toDate, graphConfig.data);
|
2023-12-21 11:25:25 +00:00
|
|
|
|
} else {
|
2024-01-06 08:47:11 +00:00
|
|
|
|
return generateByData(graphConfig.data);
|
2023-12-21 11:25:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getCellRules(graphConfig: ContributionGraphConfig) {
|
|
|
|
|
|
return graphConfig.cellStyleRules &&
|
|
|
|
|
|
graphConfig.cellStyleRules.length > 0
|
|
|
|
|
|
? graphConfig.cellStyleRules
|
|
|
|
|
|
: DEFAULT_RULES;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bindMonthTips(
|
|
|
|
|
|
monthCell: HTMLElement,
|
|
|
|
|
|
contributionItem: ContributionCellData,
|
|
|
|
|
|
contributionMapByYearMonth: Map<string, number>
|
|
|
|
|
|
) {
|
2023-12-25 14:32:05 +00:00
|
|
|
|
const yearMonth = `${contributionItem.year}-${
|
|
|
|
|
|
contributionItem.month + 1
|
|
|
|
|
|
}`;
|
2023-12-21 11:25:25 +00:00
|
|
|
|
const yearMonthValue = contributionMapByYearMonth.get(yearMonth) || 0;
|
|
|
|
|
|
// tips event
|
2024-01-06 08:47:11 +00:00
|
|
|
|
setTooltip(
|
|
|
|
|
|
monthCell,
|
|
|
|
|
|
`${yearMonthValue} contributions on ${yearMonth}.`
|
|
|
|
|
|
);
|
2023-12-21 11:25:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-27 04:34:12 +00:00
|
|
|
|
applyCellGlobalStyle(
|
|
|
|
|
|
cellEl: HTMLElement,
|
|
|
|
|
|
graphConfig: ContributionGraphConfig
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (graphConfig.cellStyle) {
|
|
|
|
|
|
Object.assign(cellEl.style, graphConfig.cellStyle);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-25 14:32:05 +00:00
|
|
|
|
applyCellStyleRule(
|
|
|
|
|
|
cellEl: HTMLElement,
|
|
|
|
|
|
contributionItem: ContributionCellData,
|
2024-01-16 10:52:03 +00:00
|
|
|
|
cellRules: CellStyleRule[],
|
|
|
|
|
|
defaultCellStyleRule?: () => CellStyleRule
|
2023-12-25 14:32:05 +00:00
|
|
|
|
) {
|
|
|
|
|
|
const cellStyleRule = matchCellStyleRule(
|
|
|
|
|
|
contributionItem.value,
|
|
|
|
|
|
cellRules
|
|
|
|
|
|
);
|
2024-01-16 10:52:03 +00:00
|
|
|
|
if (cellStyleRule != null) {
|
|
|
|
|
|
cellEl.style.backgroundColor = cellStyleRule.color;
|
|
|
|
|
|
cellEl.innerText = cellStyleRule.text || "";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (defaultCellStyleRule) {
|
|
|
|
|
|
const defaultRule = defaultCellStyleRule();
|
|
|
|
|
|
cellEl.style.backgroundColor = defaultRule.color;
|
|
|
|
|
|
cellEl.innerText = defaultRule.text || "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-25 14:32:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-21 11:25:25 +00:00
|
|
|
|
bindCellAttribute(
|
|
|
|
|
|
cellEl: HTMLElement,
|
|
|
|
|
|
contributionItem: ContributionCellData
|
|
|
|
|
|
) {
|
|
|
|
|
|
cellEl.setAttribute("data-year", contributionItem.year.toString());
|
|
|
|
|
|
cellEl.setAttribute("data-month", contributionItem.month.toString());
|
|
|
|
|
|
cellEl.setAttribute("data-date", contributionItem.date.toString());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bindCellClickEvent(
|
|
|
|
|
|
cellEl: HTMLElement,
|
|
|
|
|
|
contributionItem: ContributionCellData,
|
2024-01-10 01:51:08 +00:00
|
|
|
|
graphConfig: ContributionGraphConfig,
|
|
|
|
|
|
activityContainer?: HTMLElement
|
2023-12-21 11:25:25 +00:00
|
|
|
|
) {
|
|
|
|
|
|
cellEl.onclick = (event: MouseEvent) => {
|
|
|
|
|
|
if (graphConfig.onCellClick) {
|
|
|
|
|
|
graphConfig.onCellClick(contributionItem, event);
|
|
|
|
|
|
}
|
2024-01-10 01:51:08 +00:00
|
|
|
|
|
|
|
|
|
|
if (activityContainer) {
|
|
|
|
|
|
this.renderActivity(
|
|
|
|
|
|
graphConfig,
|
|
|
|
|
|
contributionItem,
|
|
|
|
|
|
activityContainer
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2023-12-21 11:25:25 +00:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bindCellTips(cellEl: HTMLElement, contributionItem: ContributionCellData) {
|
|
|
|
|
|
cellEl.addEventListener("mouseenter", (event) => {
|
|
|
|
|
|
const summary = contributionItem.summary
|
|
|
|
|
|
? contributionItem.summary
|
|
|
|
|
|
: `${contributionItem.value} contributions on ${contributionItem.date}.`;
|
2024-01-06 08:47:11 +00:00
|
|
|
|
setTooltip(cellEl, summary);
|
2023-12-21 11:25:25 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-01-10 01:51:08 +00:00
|
|
|
|
|
|
|
|
|
|
function renderActivityItem(items: ContributionItem[], listMain: HTMLElement) {
|
|
|
|
|
|
(items || []).slice(0, 10).forEach((item) => {
|
|
|
|
|
|
const listItem = createDiv({
|
|
|
|
|
|
cls: "activity-item",
|
|
|
|
|
|
parent: listMain,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const linkEl = createEl("a", {
|
|
|
|
|
|
text: item.label,
|
|
|
|
|
|
href: "#",
|
|
|
|
|
|
parent: listItem,
|
|
|
|
|
|
cls: "label",
|
|
|
|
|
|
});
|
|
|
|
|
|
linkEl.onclick = (event) => {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
if (item.open) {
|
|
|
|
|
|
item.open(event);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|