diff --git a/src/Heatmap.css b/src/Heatmap.css index bcb5baa..0b5b8a2 100644 --- a/src/Heatmap.css +++ b/src/Heatmap.css @@ -19,6 +19,10 @@ svg.react-calendar-heatmap { font-size: 5px; } +.react-calendar-heatmap rect { + @apply outline-none; +} + .react-calendar-heatmap rect:hover { stroke: #555 !important; stroke-width: 1px; @@ -28,6 +32,10 @@ svg.react-calendar-heatmap { stroke: #dd7c09 !important; } +.react-calendar-heatmap rect.active { + stroke: rgb(255, 0, 200) !important; +} + /* * Default color scale */ diff --git a/src/Heatmap.tsx b/src/Heatmap.tsx index 3f828cb..a5d6a4c 100644 --- a/src/Heatmap.tsx +++ b/src/Heatmap.tsx @@ -3,7 +3,6 @@ import { addWeeks, differenceInDays, endOfWeek, - parse, startOfWeek, } from "date-fns"; import * as React from "react"; @@ -16,6 +15,8 @@ import { formatAsLocale, formatAsParam, getIconPosition as getTriggerIconPosition, + parseJournalDate, + useCurrentJournalDate, } from "./utils"; const useActivities = (startDate: string, endDate: string) => { @@ -23,6 +24,9 @@ const useActivities = (startDate: string, endDate: string) => { { date: string; originalName: string; count: number }[] >([]); const isMounted = useMountedState(); + const currentJournalDate = useCurrentJournalDate(); + + const [rawValue, setRawValue] = React.useState([]); React.useLayoutEffect(() => { (async () => { @@ -38,44 +42,61 @@ const useActivities = (startDate: string, endDate: string) => { [(>= ?d ${formatAsParam(date0)})] [(<= ?d ${formatAsParam(date1)})]] `); - const mapping = Object.fromEntries( - res.map(([page, count]: any[]) => { - const datum = { - count: count ?? 0, - date: formatAsDashed( - parse(`${page["journal-day"]}`, "yyyyMMdd", new Date()) - ), - originalName: page["original-name"] as string, - }; - return [datum.date, datum]; - }) - ); - - const totalDays = differenceInDays(date1, date0) + 1; - const newValues: Datum[] = []; - for (let i = 0; i < totalDays; i++) { - const date = formatAsDashed(addDays(date0, i)); - if (mapping[date]) { - newValues.push(mapping[date]); - } else { - newValues.push({ - date, - count: 0, - originalName: formatAsLocale(date), - }); - } - } - if (isMounted()) { - setValues(newValues); + setRawValue(res); } })(); }, [startDate, endDate]); - return values; + return React.useMemo(() => { + const date0 = new Date(startDate); + const date1 = new Date(endDate); + const mapping = Object.fromEntries( + rawValue.map(([page, count]: any[]) => { + const date = parseJournalDate(page["journal-day"]); + const datum = { + count: count ?? 0, + date: formatAsDashed(date), + originalName: page["original-name"] as string, + }; + return [datum.date, datum]; + }) + ); + + const totalDays = differenceInDays(date1, date0) + 1; + const newValues: Datum[] = []; + for (let i = 0; i < totalDays; i++) { + const date = formatAsDashed(addDays(date0, i)); + if (mapping[date]) { + newValues.push(mapping[date]); + } else { + newValues.push({ + date, + count: 0, + originalName: formatAsLocale(date), + }); + } + } + + if (currentJournalDate) { + const datum = newValues.find( + (v) => formatAsDashed(currentJournalDate) === v.date + ); + if (datum) { + datum.isActive = true; + } + } + + return newValues; + }, [rawValue, currentJournalDate]); }; -type Datum = ReturnType[number]; +type Datum = { + date: string; + originalName: string; + count: number; + isActive?: boolean; +}; // We have 1 ~ 4 scales for now: // [1, 10] -> 1 @@ -132,13 +153,17 @@ const HeatmapChart = ({ if (today === value?.date) { classes.push("today"); } + if (value?.isActive) { + classes.push("active"); + } return classes.join(" "); }} tooltipDataAttrs={getTooltipDataAttrs} onClick={(d: Datum) => { if (d) { logseq.App.pushState("page", { name: d.originalName }); - logseq.hideMainUI(); + // Allow the user to quickly navigate between different days + // logseq.hideMainUI(); } }} gutterSize={4} diff --git a/src/main.tsx b/src/main.tsx index bab9cf2..e43b59a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -44,7 +44,7 @@ function main() { div[data-injected-ui=${openIconName}-${pluginId}] .logseq-heatmap-trigger-icon { width: 1em; height: 1em; - margin: 1px 0.4em 0 0.4em; + margin: 2px 0.4em 0 0.4em; background-color: #26a641; border-radius: 4px; border: 1px solid #eee; diff --git a/src/utils.ts b/src/utils.ts index 35325c9..8547e3d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ -import { format } from "date-fns"; +import { format, parse } from "date-fns"; import React, { useState } from "react"; import { useMountedState } from "react-use"; +import { BlockEntity, PageEntity } from "@logseq/libs/dist/LSPlugin"; export const openIconName = "heatmap-plugin-open"; @@ -43,6 +44,28 @@ export const useSidebarVisible = () => { return visible; }; +export const useCurrentPage = () => { + const [page, setPage] = React.useState(null); + const setActivePage = async () => { + const p = await logseq.Editor.getCurrentPage(); + setPage(p); + }; + React.useEffect(() => { + return logseq.App.onRouteChanged(setActivePage); + }, [setActivePage]); + return page; +}; + +export const useCurrentJournalDate = () => { + const page = useCurrentPage(); + return React.useMemo(() => { + if (page && page["journal?"] && page.journalDay) { + return parseJournalDate(page.journalDay); + } + return null; + }, [page]); +}; + export const useThemeMode = () => { const isMounted = useMountedState(); const [mode, setMode] = React.useState<"dark" | "light">("light"); @@ -51,7 +74,7 @@ export const useThemeMode = () => { (top?.document .querySelector("html") ?.getAttribute("data-theme") as typeof mode) ?? - (matchMedia("prefers-color-scheme: dark").matches ? "dark" : "light") + (matchMedia("prefers-color-scheme: dark").matches ? "dark" : "light") ); logseq.App.onThemeModeChanged((s) => { console.log(s); @@ -64,7 +87,6 @@ export const useThemeMode = () => { return mode; }; - export let displayDateFormat = "MMM do, yyyy"; export async function getDisplayDateFormat() { @@ -92,4 +114,8 @@ export const formatAsParam = (d: Date | string) => { export const formatAsLocale = (d: Date | string) => { return format(toDate(d), displayDateFormat); -}; \ No newline at end of file +}; + +export const parseJournalDate = (d: number) => { + return parse(`${d}`, "yyyyMMdd", new Date()); +};