Skip to content

Commit ab88bad

Browse files
committed
feat: update to latest features
1 parent 9f292e4 commit ab88bad

9 files changed

Lines changed: 8049 additions & 298 deletions

File tree

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import {
2+
Component,
3+
ExtraButtonComponent,
4+
setIcon,
5+
DropdownComponent,
6+
ButtonComponent,
7+
App,
8+
moment,
9+
setTooltip,
10+
} from "obsidian";
11+
import { t } from "../../translations/helper";
12+
import "../../styles/date-picker.css";
13+
import type TaskProgressBarPlugin from "../../index";
14+
15+
export interface DatePickerState {
16+
selectedDate: string | null;
17+
dateMark: string;
18+
}
19+
20+
export class DatePickerComponent extends Component {
21+
private hostEl: HTMLElement;
22+
private app: App;
23+
private plugin?: TaskProgressBarPlugin;
24+
private state: DatePickerState;
25+
private onDateChange?: (date: string) => void;
26+
private currentViewDate: moment.Moment;
27+
28+
constructor(
29+
hostEl: HTMLElement,
30+
app: App,
31+
plugin?: TaskProgressBarPlugin,
32+
initialDate?: string,
33+
dateMark: string = "📅"
34+
) {
35+
super();
36+
this.hostEl = hostEl;
37+
this.app = app;
38+
this.plugin = plugin;
39+
this.state = {
40+
selectedDate: initialDate || null,
41+
dateMark: dateMark,
42+
};
43+
this.currentViewDate = initialDate ? moment(initialDate) : moment();
44+
}
45+
46+
onload(): void {
47+
this.render();
48+
}
49+
50+
onunload(): void {
51+
this.hostEl.empty();
52+
}
53+
54+
setOnDateChange(callback: (date: string) => void): void {
55+
this.onDateChange = callback;
56+
}
57+
58+
getSelectedDate(): string | null {
59+
return this.state.selectedDate;
60+
}
61+
62+
setSelectedDate(date: string | null): void {
63+
this.state.selectedDate = date;
64+
this.updateSelectedDateDisplay();
65+
if (this.onDateChange && date) {
66+
this.onDateChange(`${this.state.dateMark} ${date}`);
67+
}
68+
}
69+
70+
private render(): void {
71+
this.hostEl.empty();
72+
this.hostEl.addClass("date-picker-root-container");
73+
74+
const mainPanel = this.hostEl.createDiv({
75+
cls: "date-picker-main-panel",
76+
});
77+
78+
// Create two-column layout
79+
const leftPanel = mainPanel.createDiv({
80+
cls: "date-picker-left-panel",
81+
});
82+
83+
const rightPanel = mainPanel.createDiv({
84+
cls: "date-picker-right-panel",
85+
});
86+
87+
this.renderQuickOptions(leftPanel);
88+
this.renderCalendar(rightPanel);
89+
}
90+
91+
private renderQuickOptions(container: HTMLElement): void {
92+
const quickOptionsContainer = container.createDiv({
93+
cls: "quick-options-container",
94+
});
95+
96+
// Add quick date options
97+
const quickOptions = [
98+
{ amount: 0, unit: "days", label: t("Today") },
99+
{ amount: 1, unit: "days", label: t("Tomorrow") },
100+
{ amount: 2, unit: "days", label: t("In 2 days") },
101+
{ amount: 3, unit: "days", label: t("In 3 days") },
102+
{ amount: 5, unit: "days", label: t("In 5 days") },
103+
{ amount: 1, unit: "weeks", label: t("In 1 week") },
104+
{ amount: 10, unit: "days", label: t("In 10 days") },
105+
{ amount: 2, unit: "weeks", label: t("In 2 weeks") },
106+
{ amount: 1, unit: "months", label: t("In 1 month") },
107+
{ amount: 2, unit: "months", label: t("In 2 months") },
108+
{ amount: 3, unit: "months", label: t("In 3 months") },
109+
{ amount: 6, unit: "months", label: t("In 6 months") },
110+
{ amount: 1, unit: "years", label: t("In 1 year") },
111+
];
112+
113+
quickOptions.forEach((option) => {
114+
const optionEl = quickOptionsContainer.createDiv({
115+
cls: "quick-option-item",
116+
});
117+
118+
optionEl.createSpan({
119+
text: option.label,
120+
cls: "quick-option-label",
121+
});
122+
123+
const date = moment().add(
124+
option.amount,
125+
option.unit as moment.unitOfTime.DurationConstructor
126+
);
127+
const formattedDate = date.format("YYYY-MM-DD");
128+
129+
optionEl.createSpan({
130+
text: formattedDate,
131+
cls: "quick-option-date",
132+
});
133+
134+
this.registerDomEvent(optionEl, "click", (e) => {
135+
e.preventDefault();
136+
e.stopPropagation();
137+
this.setSelectedDate(formattedDate);
138+
});
139+
140+
// Highlight if this is the selected date
141+
if (this.state.selectedDate === formattedDate) {
142+
optionEl.addClass("selected");
143+
}
144+
});
145+
146+
// Add clear option
147+
const clearOption = container.createDiv({
148+
cls: "quick-option-item clear-option",
149+
});
150+
151+
clearOption.createSpan({
152+
text: t("Clear Date"),
153+
cls: "quick-option-label",
154+
});
155+
156+
this.registerDomEvent(clearOption, "click", (e) => {
157+
e.preventDefault();
158+
e.stopPropagation();
159+
this.setSelectedDate(null);
160+
});
161+
}
162+
163+
private renderCalendar(container: HTMLElement): void {
164+
const calendarContainer = container.createDiv({
165+
cls: "calendar-container",
166+
});
167+
168+
this.renderCalendarHeader(calendarContainer, this.currentViewDate);
169+
this.renderCalendarGrid(calendarContainer, this.currentViewDate);
170+
}
171+
172+
private renderCalendarHeader(
173+
container: HTMLElement,
174+
currentDate: moment.Moment
175+
): void {
176+
const header = container.createDiv({
177+
cls: "calendar-header",
178+
});
179+
180+
// Previous month button
181+
const prevBtn = header.createDiv({
182+
cls: "calendar-nav-btn",
183+
});
184+
setIcon(prevBtn, "chevron-left");
185+
this.registerDomEvent(prevBtn, "click", (e) => {
186+
e.preventDefault();
187+
e.stopPropagation();
188+
this.navigateMonth(-1);
189+
});
190+
191+
// Month/Year display
192+
const monthYear = header.createDiv({
193+
cls: "calendar-month-year",
194+
text: currentDate.format("MMMM YYYY"),
195+
});
196+
197+
// Next month button
198+
const nextBtn = header.createDiv({
199+
cls: "calendar-nav-btn",
200+
});
201+
setIcon(nextBtn, "chevron-right");
202+
this.registerDomEvent(nextBtn, "click", (e) => {
203+
e.preventDefault();
204+
e.stopPropagation();
205+
this.navigateMonth(1);
206+
});
207+
}
208+
209+
private renderCalendarGrid(
210+
container: HTMLElement,
211+
currentDate: moment.Moment
212+
): void {
213+
const grid = container.createDiv({
214+
cls: "calendar-grid",
215+
});
216+
217+
// Day headers
218+
const dayHeaders = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
219+
dayHeaders.forEach((day) => {
220+
grid.createDiv({
221+
cls: "calendar-day-header",
222+
text: day,
223+
});
224+
});
225+
226+
// Get first day of month and number of days
227+
const firstDay = currentDate.clone().startOf("month");
228+
const lastDay = currentDate.clone().endOf("month");
229+
const startDate = firstDay.clone().startOf("week");
230+
const endDate = lastDay.clone().endOf("week");
231+
232+
// Generate calendar days
233+
const current = startDate.clone();
234+
while (current.isSameOrBefore(endDate)) {
235+
const dayEl = grid.createDiv({
236+
cls: "calendar-day",
237+
text: current.format("D"),
238+
});
239+
240+
const dateStr = current.format("YYYY-MM-DD");
241+
242+
// Add classes for styling
243+
if (!current.isSame(firstDay, "month")) {
244+
dayEl.addClass("other-month");
245+
}
246+
247+
if (current.isSame(moment(), "day")) {
248+
dayEl.addClass("today");
249+
}
250+
251+
if (this.state.selectedDate === dateStr) {
252+
dayEl.addClass("selected");
253+
}
254+
255+
// Add click handler
256+
this.registerDomEvent(dayEl, "click", (e) => {
257+
e.preventDefault();
258+
e.stopPropagation();
259+
this.setSelectedDate(dateStr);
260+
});
261+
262+
current.add(1, "day");
263+
}
264+
}
265+
266+
private navigateMonth(direction: number): void {
267+
console.log(`Navigating month: ${direction}`);
268+
this.currentViewDate.add(direction, "month");
269+
this.render();
270+
}
271+
272+
private updateSelectedDateDisplay(): void {
273+
// Update the visual state of selected items
274+
this.hostEl.querySelectorAll(".selected").forEach((el) => {
275+
el.removeClass("selected");
276+
});
277+
278+
if (this.state.selectedDate) {
279+
// Highlight selected quick option
280+
this.hostEl.querySelectorAll(".quick-option-item").forEach((el) => {
281+
const dateSpan = el.querySelector(".quick-option-date");
282+
if (
283+
dateSpan &&
284+
dateSpan.textContent === this.state.selectedDate
285+
) {
286+
el.addClass("selected");
287+
}
288+
});
289+
290+
// Highlight selected calendar day
291+
this.hostEl.querySelectorAll(".calendar-day").forEach((el) => {
292+
const dayText = el.textContent;
293+
if (dayText && this.state.selectedDate) {
294+
const selectedMoment = moment(this.state.selectedDate);
295+
if (dayText === selectedMoment.format("D")) {
296+
el.addClass("selected");
297+
}
298+
}
299+
});
300+
}
301+
}
302+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { App, Modal } from "obsidian";
2+
import { DatePickerComponent, DatePickerState } from "./DatePickerComponent";
3+
import type TaskProgressBarPlugin from "../../index";
4+
5+
export class DatePickerModal extends Modal {
6+
public datePickerComponent: DatePickerComponent;
7+
public onDateSelected: ((date: string | null) => void) | null = null;
8+
private plugin?: TaskProgressBarPlugin;
9+
private initialDate?: string;
10+
private dateMark: string;
11+
12+
constructor(
13+
app: App,
14+
plugin?: TaskProgressBarPlugin,
15+
initialDate?: string,
16+
dateMark: string = "📅"
17+
) {
18+
super(app);
19+
this.plugin = plugin;
20+
this.initialDate = initialDate;
21+
this.dateMark = dateMark;
22+
}
23+
24+
onOpen() {
25+
const { contentEl } = this;
26+
contentEl.empty();
27+
28+
this.datePickerComponent = new DatePickerComponent(
29+
this.contentEl,
30+
this.app,
31+
this.plugin,
32+
this.initialDate,
33+
this.dateMark
34+
);
35+
36+
this.datePickerComponent.onload();
37+
38+
// Set up date change callback
39+
this.datePickerComponent.setOnDateChange((date: string) => {
40+
if (this.onDateSelected) {
41+
this.onDateSelected(date);
42+
}
43+
this.close();
44+
});
45+
}
46+
47+
onClose() {
48+
const { contentEl } = this;
49+
50+
if (this.datePickerComponent) {
51+
this.datePickerComponent.onunload();
52+
}
53+
54+
contentEl.empty();
55+
}
56+
}

0 commit comments

Comments
 (0)