mirror of
https://github.com/vran-dev/obsidian-contribution-graph.git
synced 2026-07-22 09:20:23 +00:00
303 lines
7.1 KiB
TypeScript
303 lines
7.1 KiB
TypeScript
import { DEFAULT_RULES } from "src/constants";
|
||
import {
|
||
ContributionGraphConfig,
|
||
ContributionCellData,
|
||
CellStyleRule,
|
||
ContributionItem,
|
||
} from "src/types";
|
||
import { parseDate } from "src/util/dateUtils";
|
||
import {
|
||
generateByLatestDays,
|
||
generateByFixedDate,
|
||
generateByData,
|
||
} from "./matrixDataGenerator";
|
||
import { matchCellStyleRule } from "src/util/utils";
|
||
import { setTooltip } from "obsidian";
|
||
import { Locals } from "src/i18/messages";
|
||
|
||
export interface GraphRender {
|
||
render(container: HTMLElement, graphConfig: ContributionGraphConfig): void;
|
||
|
||
graphType(): string;
|
||
}
|
||
|
||
export abstract class BaseGraphRender implements GraphRender {
|
||
constructor() {}
|
||
|
||
render(container: HTMLElement, graphConfig: ContributionGraphConfig): void {
|
||
throw new Error("Method not implemented.");
|
||
}
|
||
|
||
abstract graphType(): string;
|
||
|
||
renderTitle(
|
||
graphConfig: ContributionGraphConfig,
|
||
parent: HTMLElement
|
||
): HTMLElement {
|
||
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);
|
||
return titleEl;
|
||
}
|
||
|
||
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
|
||
const summary = `${rule.min} ≤ contributions < ${rule.max}`;
|
||
setTooltip(cellEl, summary);
|
||
});
|
||
createDiv({
|
||
cls: "cell text",
|
||
text: "more",
|
||
parent: cellRuleIndicatorContainer,
|
||
});
|
||
}
|
||
|
||
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();
|
||
|
||
const closeButton = createEl("button", {
|
||
cls: "close-button",
|
||
text: 'x',
|
||
parent: contaienr,
|
||
})
|
||
|
||
closeButton.onclick = () => {
|
||
contaienr.empty();
|
||
}
|
||
|
||
let summary;
|
||
if (cellData.value > 0) {
|
||
summary = Locals.get().you_have_contributed_to
|
||
.replace("{date}", cellData.date)
|
||
.replace("{value}", cellData.value.toString());
|
||
} else {
|
||
summary = Locals.get().you_have_no_contributions_on
|
||
.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", {
|
||
text: Locals.get().click_to_load_more,
|
||
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();
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
generateContributionData(graphConfig: ContributionGraphConfig) {
|
||
if (graphConfig.days) {
|
||
return generateByLatestDays(graphConfig.days, graphConfig.data);
|
||
} else if (graphConfig.fromDate && graphConfig.toDate) {
|
||
const fromDate = parseDate(graphConfig.fromDate);
|
||
const toDate = parseDate(graphConfig.toDate);
|
||
return generateByFixedDate(fromDate, toDate, graphConfig.data);
|
||
} else {
|
||
return generateByData(graphConfig.data);
|
||
}
|
||
}
|
||
|
||
getCellRules(graphConfig: ContributionGraphConfig) {
|
||
return graphConfig.cellStyleRules &&
|
||
graphConfig.cellStyleRules.length > 0
|
||
? graphConfig.cellStyleRules
|
||
: DEFAULT_RULES;
|
||
}
|
||
|
||
bindMonthTips(
|
||
monthCell: HTMLElement,
|
||
contributionItem: ContributionCellData,
|
||
contributionMapByYearMonth: Map<string, number>
|
||
) {
|
||
const yearMonth = `${contributionItem.year}-${
|
||
contributionItem.month + 1
|
||
}`;
|
||
const yearMonthValue = contributionMapByYearMonth.get(yearMonth) || 0;
|
||
// tips event
|
||
setTooltip(
|
||
monthCell,
|
||
`${yearMonthValue} contributions on ${yearMonth}.`
|
||
);
|
||
}
|
||
|
||
applyCellGlobalStyle(
|
||
cellEl: HTMLElement,
|
||
graphConfig: ContributionGraphConfig
|
||
) {
|
||
if (graphConfig.cellStyle) {
|
||
Object.assign(cellEl.style, graphConfig.cellStyle);
|
||
}
|
||
}
|
||
|
||
applyCellStyleRule(
|
||
cellEl: HTMLElement,
|
||
contributionItem: ContributionCellData,
|
||
cellRules: CellStyleRule[],
|
||
defaultCellStyleRule?: () => CellStyleRule
|
||
) {
|
||
const cellStyleRule = matchCellStyleRule(
|
||
contributionItem.value,
|
||
cellRules
|
||
);
|
||
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 || "";
|
||
}
|
||
|
||
}
|
||
|
||
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,
|
||
graphConfig: ContributionGraphConfig,
|
||
activityContainer?: HTMLElement
|
||
) {
|
||
cellEl.onclick = (event: MouseEvent) => {
|
||
if (graphConfig.onCellClick) {
|
||
graphConfig.onCellClick(contributionItem, event);
|
||
}
|
||
|
||
if (activityContainer) {
|
||
this.renderActivity(
|
||
graphConfig,
|
||
contributionItem,
|
||
activityContainer
|
||
);
|
||
}
|
||
};
|
||
}
|
||
|
||
bindCellTips(cellEl: HTMLElement, contributionItem: ContributionCellData) {
|
||
cellEl.addEventListener("mouseenter", (event) => {
|
||
const summary = contributionItem.summary
|
||
? contributionItem.summary
|
||
: `${contributionItem.value} contributions on ${contributionItem.date}.`;
|
||
setTooltip(cellEl, summary);
|
||
});
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
};
|
||
});
|
||
}
|