This commit is contained in:
lda committed 2025-05-22 23:23:11 +07:00
1 parent 7c8c100f52
commit 9100207c59
4 files changed
+230 -46

No files matched your search

+155
View File
@@ -15,3 +15,158 @@ git clone this repo
npm install
npm run dev
```
## ass pull
Got it! Let’s create a **long-term plan** to improve your project. The goal is to make it feature-rich, maintainable, and user-friendly while leveraging FullCalendar’s capabilities and your existing backend.
---
### **Long-Term Plan**
#### **1. Frontend Filtering**
Move filtering logic (date, regex, etc.) to the frontend using FullCalendar’s `getEvents()` and `eventDataTransform`.
##### **Steps:**
1. Use `getEvents()` to retrieve all events from the calendar.
2. Apply filters (date range, regex, etc.) directly on the frontend.
3. Add a UI for users to specify filters (e.g., date pickers, regex input).
4. Use `eventDataTransform` to preprocess events when they are fetched.
---
#### **2. Loading State**
Replace the current placeholder/spinner logic with FullCalendar’s built-in `loading` callback.
##### **Steps:**
1. Use the `loading` callback to show/hide a spinner or loading message.
2. Remove manual spinner logic from your code.
---
#### **3. List of Event Sources**
Display a list of all added event sources and allow users to:
- Remove sources.
- Refetch events from a source.
- Toggle visibility of events from a source.
##### **Steps:**
1. Use `getEventSources()` to retrieve all sources.
2. Display them in a list with controls (e.g., remove, refetch, toggle).
3. Use `EventSource::remove()` and `EventSource::refetch()` for interactivity.
---
#### **4. Improve CORS Handling**
If the backend is only for bypassing Google Calendar’s CORS issues, consider:
- Adding a proxy endpoint for other calendar services.
- Allowing users to directly fetch events from public calendars without needing the backend.
---
#### **5. Modularize Calendar Configuration**
Centralize FullCalendar configuration into a reusable function or object.
##### **Steps:**
1. Create a `getCalendarConfig()` function that returns the calendar configuration.
2. Pass dynamic options (e.g., theme, initial view) as arguments.
---
#### **6. Add Advanced Features**
Enhance the app with the following features:
- **Event Editing**: Allow users to edit event details (e.g., title, date).
- **Event Creation**: Add a form to create new events.
- **Export Events**: Allow users to export filtered events as `.ics` files.
---
#### **7. Improve UI/UX**
Make the app more user-friendly:
- Add tooltips for buttons and inputs.
- Improve responsiveness for mobile devices.
- Add a dark mode toggle.
---
#### **8. Clean Up CSS**
- Remove hacky CSS rules (e.g., `padding-bottom: 1em` for `#calendar`).
- Use consistent spacing and alignment.
- Add comments to explain key styles.
---
#### **9. Testing**
Add automated tests for:
- Filtering logic.
- Event source management.
- UI interactions.
---
### **Implementation Plan**
#### **Phase 1: Frontend Filtering**
1. Add a filter UI (date pickers, regex input).
2. Use `getEvents()` to retrieve events and apply filters.
3. Update the calendar dynamically based on the filtered events.
---
#### **Phase 2: Loading State**
1. Replace manual spinner logic with FullCalendar’s `loading` callback.
2. Test loading behavior with multiple event sources.
---
#### **Phase 3: Event Source Management**
1. Use `getEventSources()` to display a list of sources.
2. Add controls for removing, refetching, and toggling sources.
---
#### **Phase 4: Advanced Features**
1. Add event editing and creation forms.
2. Implement event export functionality.
---
#### **Phase 5: UI/UX Improvements**
1. Add tooltips and improve responsiveness.
2. Implement dark mode.
---
#### **Phase 6: Cleanup and Testing**
1. Refactor CSS and JavaScript for maintainability.
2. Add automated tests.
---
### **Next Steps**
Let me know which phase you’d like to start with, and I’ll help you implement it step by step!
+20 -17
View File
@@ -7,7 +7,9 @@ import timeGridPlugin from "@fullcalendar/timegrid";
import bootstrap5Plugin from "@fullcalendar/bootstrap5";
import { getNextColor } from "./color_generator";
import { formatTooltip } from "./tooltip_parser";
// import { formatTooltip } from "./tooltip_parser";
import { glued } from "./main";
let calendar = null;
@@ -31,7 +33,7 @@ export function initializeCalendar(
dayGridPlugin,
],
themeSystem: "bootstrap5",
initialView: "listWeek",
initialView: "listWeek", // trololo
navLinks: true,
dayMaxEvents: true,
nowIndicator: true,
@@ -47,28 +49,29 @@ export function initializeCalendar(
timeGridDay: { buttonText: "Day" },
listMonth: { buttonText: "List" },
},
events: {
url: url,
format: "ics",
color: getNextColor(),
},
events: init_ics_source(url),
eventMouseEnter: onEventMouseEnter,
eventMouseLeave: onEventMouseLeave,
loading: glued,
});
// console.log(calendar);
calendar.render();
}
export function addEventSource(url) {
export const addEventSource = (url) => {
if (calendar) {
calendar.addEventSource({
url: url,
format: "ics",
color: getNextColor(),
});
calendar.addEventSource(init_ics_source(url));
}
}
};
export function getCalendarInstance() {
export const getCalendarInstance = () => {
return calendar;
}
};
const init_ics_source = (url) => {
return {
url: url,
format: "ics",
color: getNextColor(),
};
};
+37 -16
View File
@@ -1,4 +1,8 @@
import { initializeCalendar, addEventSource, getCalendarInstance } from "./calendar";
import {
initializeCalendar,
addEventSource,
getCalendarInstance,
} from "./calendar";
import { newTippy, tippyDie } from "./tooltip";
import { create_url } from "./utils";
@@ -30,23 +34,32 @@ function updateEventSourceList(calendar) {
eventSourceList.innerHTML = "";
// Fetch all event sources
const sources = calendar.getEventSources();
sources.forEach((source) => {
calendar.getEventSources().forEach((source) => {
const card = document.createElement("div");
card.className = "event-source-card";
card.style.display = "flex";
card.style.justifyContent = "space-between";
card.style.alignItems = "center";
card.style.borderLeft = `4px solid ${source.color || "#007bff"}`;
card.style.padding = "0.5em";
card.style.marginBottom = "0.5em";
// Create info container
const info = document.createElement("div");
const url = source.url || "(No URL)";
const format = source.format || "(Unknown Format)";
// Add source info
card.innerHTML = `
<div><strong>Source:</strong> ${url}<br>
<strong>Format:</strong> ${format}</div>
`;
const sourceInfo = document.createElement("div");
sourceInfo.innerHTML = `<strong>Source:</strong> ${url}<br><strong>Format:</strong> ${format}`;
const loadingStatus = document.createElement("div");
loadingStatus.className = "loading-status";
loadingStatus.textContent = source.internalEventSource.isFetching
? "Fetching in progress"
: "Loaded";
info.appendChild(sourceInfo);
info.appendChild(loadingStatus);
// Add Refetch Button
const refetchButton = document.createElement("button");
@@ -67,15 +80,26 @@ function updateEventSourceList(calendar) {
updateEventSourceList(calendar); // Refresh the list after removal
});
// Append buttons to the card
card.appendChild(refetchButton);
card.appendChild(removeButton);
// Create a button container
const buttonContainer = document.createElement("div");
buttonContainer.appendChild(refetchButton);
buttonContainer.appendChild(removeButton);
// Append info and buttons to the card
card.appendChild(info);
card.appendChild(buttonContainer);
// Append the card to the event source list
eventSourceList.appendChild(card);
});
}
export const glued = (e) => {
// console.log("loading:", e); // half of it will be true false and half of it will be events
setTimeout(() => updateEventSourceList(getCalendarInstance()));
};
confirm_button.addEventListener("click", () => {
const url = create_url(text_field.value);
if (!url && text_field.value) return alert("Please input valid url.");
@@ -86,7 +110,7 @@ confirm_button.addEventListener("click", () => {
}
hidePlaceholder();
initializeCalendar(cal_ele, url, newTippy, tippyDie);
initializeCalendar(cal_ele, url, newTippy, tippyDie);
});
add_button.addEventListener("click", () => {
@@ -97,6 +121,3 @@ add_button.addEventListener("click", () => {
}
addEventSource(url);
});
document.getElementById("event-source-management").addEventListener("click", _ => updateEventSourceList(getCalendarInstance()));
+18 -13
View File
@@ -11,13 +11,16 @@ body {
justify-content: center;
align-items: center;
min-height: 100vh;
/* max-width: 900px; */
/* Removed to allow full responsiveness */
}
.container {
max-width: 900px;
width: 100%;
padding: 1em;
box-sizing: border-box;
/* max-width: 900px; */
/* Removed to allow Bootstrap container to handle layout */
}
.app-header {
@@ -29,8 +32,9 @@ body {
display: flex;
gap: 0.5em;
margin: 2em auto 1em auto;
max-width: 900px;
align-items: center;
/* max-width: 900px; */
/* Removed to allow full responsiveness */
}
#ics_file {
@@ -60,30 +64,24 @@ input[type="button"]:hover {
}
#calendar {
/* max-width: 900px; */
/* margin: 0 auto 2em auto; */
/* background: #fff; */
/* border-radius: 8px; */
/* box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07); */
/* padding: 1em 0.5em 2em 0.5em; */
padding-bottom: 1em; /* ultra mega hack */
display: none;
}
.calendar-wrapper {
position: relative;
max-width: 900px;
margin: 0 auto 2em auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07);
padding: 1em 0.5em 1em 0.5em;
/* max-width: 900px; */
/* Removed to allow full responsiveness */
}
/* Placeholder message */
.placeholder-message {
text-align: center;
/* justify-self: center; */
color: #6c757d;
font-size: 1.2em;
margin: 1em 0;
@@ -93,16 +91,17 @@ input[type="button"]:hover {
.flex_input {
flex-direction: column;
gap: 0.7em;
max-width: 98vw;
/* max-width: 98vw; */
/* Removed to allow Bootstrap container to handle layout */
}
#calendar {
max-width: 99vw;
padding: 0.5em 0.1em 1em 0.1em;
/* max-width: 99vw; */
/* Removed to allow Bootstrap container to handle layout */
}
}
.app-footer {
/* display: flex; */
justify-items: center;
padding: 1em 0;
border-top: 1px solid #eee;
@@ -130,4 +129,10 @@ input[type="button"]:hover {
.event-source-card button {
margin-top: 0.5em;
}
.event-source-card .loading-status {
font-size: 0.9em;
color: #6c757d;
margin-top: 0.5em;
}