Project IT Calendar 1.0.0
Advanced Calendar Applet for Cinnamon Desktop Environment
Loading...
Searching...
No Matches
applet.ts
Go to the documentation of this file.
1/**
2 * Universal Calendar Applet Core
3 * ==============================
4 *
5 * This is the main entry point for the Cinnamon Calendar Applet.
6 * It orchestrates all components and manages the complete UI lifecycle.
7 *
8 * IMPORTANT: This file is COMPLETELY DIFFERENT from the simplified
9 * documentation version previously created. This is the ACTUAL production code.
10 *
11 * ------------------------------------------------------------------
12 * ARCHITECTURE OVERVIEW:
13 * ------------------------------------------------------------------
14 * This applet follows a composite MVC architecture:
15 *
16 * 1. MODEL LAYER:
17 * - EventManager: Fetches calendar data from Evolution Data Server (EDS)
18 * - CalendarLogic: Calculates dates, holidays, and business logic
19 *
20 * 2. VIEW LAYER:
21 * - CalendarView: Main calendar grid (month/year/day views)
22 * - EventListView: Sidebar event list with scrollable agenda
23 * - Header/Footer: Additional UI components for navigation
24 *
25 * 3. CONTROLLER LAYER:
26 * - This class (UniversalCalendarApplet): Coordinates everything
27 * - Settings system, hotkeys, UI layout, signal routing
28 *
29 * ------------------------------------------------------------------
30 * VISUAL LAYOUT STRUCTURE:
31 * ------------------------------------------------------------------
32 * The applet uses a sophisticated two-column layout:
33 *
34 * ┌─────────────────────────────────────────────────────────────┐
35 * │ [Event List View] │ [Calendar View + Header + Footer] │
36 * │ (Left Column) │ (Right Column) │
37 * │ • Scrollable event list│ • Date header │
38 * │ • Clickable events │ • Month grid │
39 * │ • Date navigation │ • Year view │
40 * │ │ • Day details │
41 * │ │ • Footer buttons │
42 * └─────────────────────────────────────────────────────────────┘
43 *
44 * @author Arnold Schiller <calendar@projektit.de>
45 * @link https://github.com/ArnoldSchiller/calendar
46 * @link https://projektit.de/kalender
47 * @license GPL-3.0-or-later
48 */
49/**
50 * @file applet.ts
51 * @brief Main entry point for the Cinnamon Calendar Applet
52 *
53 * @details This file implements the UniversalCalendarApplet class which acts as the
54 * central controller in the MVC architecture. It orchestrates all components and
55 * manages the complete UI lifecycle.
56 *
57 * @author Arnold Schiller <calendar@projektit.de>
58 * @date 2026
59 * @copyright GPL-3.0-or-later
60 *
61 * @see CalendarView
62 * @see EventManager
63 * @see CalendarLogic
64 * @see EventListView
65 */
66
67/* ================================================================
68 * CINNAMON / GJS IMPORTS
69 * ================================================================
70 *
71 * GJS (GNOME JavaScript) provides bindings to Cinnamon's native APIs.
72 * These are NOT npm packages - they're loaded at runtime by Cinnamon.
73 */
74
75const GLib = imports.gi.GLib; // Low-level GLib utilities (timers, file ops)
76const St = imports.gi.St; // Shell Toolkit (UI widgets)
77const Applet = imports.ui.applet; // Base applet classes
78const PopupMenu = imports.ui.popupMenu; // Popup menu system
79const Settings = imports.ui.settings; // User settings persistence
80const Main = imports.ui.main; // Main Cinnamon UI manager
81const Util = imports.misc.util; // Utility functions (spawn commands)
82const FileUtils = imports.misc.fileUtils; // File system utilities
83const Gettext = imports.gettext; // Internationalization (i18n)
84const Gtk = imports.gi.Gtk; // GTK for file dialogs (ICS import)
85const Gio = imports.gi.Gio; // GIO for file operations
86
87/* ================================================================
88 * MODULE IMPORTS (TypeScript/ES6 style)
89 * ================================================================
90 *
91 * These are local project modules. During TypeScript compilation,
92 * they're bundled together. At runtime, they're available globally.
93 */
94
95import { EventManager } from './EventManager';
96import { EventListView } from './EventListView';
97import { CalendarLogic } from './CalendarLogic';
98
99/* ================================================================
100 * INTERNATIONALIZATION (i18n) SETUP
101 * ================================================================
102 *
103 * Cinnamon applets use Gettext for translations. This system:
104 * 1. Looks for translations in the applet's locale/ directory
105 * 2. Falls back to Cinnamon's system translations
106 * 3. Falls back to GNOME Calendar translations
107 *
108 * This maximizes translation coverage with minimal effort.
109 */
110
111/**
112 * Global translation function.
113 * Must be initialized by setupLocalization() before use.
114 */
115let _: (str: string) => string;
116
117/**
118 * Initializes the translation system for this applet instance.
119 *
120 * @param uuid - Unique identifier of the applet (e.g., "calendar@projektit.de")
121 * @param path - Filesystem path to the applet directory
122 */
123function setupLocalization(uuid: string, path: string) {
124 // Bind the applet's translation domain
125 Gettext.bindtextdomain(uuid, path + "/locale");
126
127 // Create translation function with fallback chain
128 _ = function(str: string) {
129 // 1. Try applet-specific translations
130 let custom = Gettext.dgettext(uuid, str);
131 if (custom !== str) return custom;
132
133 // 2. Try Cinnamon core translations
134 let cinnamon = Gettext.dgettext("cinnamon", str);
135 if (cinnamon !== str) return cinnamon;
136
137 // 3. Fall back to GNOME Calendar translations
138 return Gettext.dgettext("gnome-calendar", str);
139 };
140}
141
142/* ================================================================
143 * MAIN APPLET CLASS
144 * ================================================================
145 *
146 * This is the central controller class that Cinnamon instantiates.
147 * One instance exists for each panel placement of the applet.
148 *
149 * Extends TextIconApplet which supports both text label and icon
150 * in the Cinnamon panel.
151 */
152/**
153 * @class UniversalCalendarApplet
154 * @extends Applet.TextIconApplet
155 * @brief Main applet controller class
156 *
157 * @details This class is instantiated by Cinnamon when the applet is loaded.
158 * It handles:
159 * - Component initialization and wiring
160 * - Settings management
161 * - UI layout assembly
162 * - Signal routing between components
163 * - Hotkey and panel integration
164 */
165
166class UniversalCalendarApplet extends Applet.TextIconApplet {
167 /* ============================================================
168 * PUBLIC PROPERTIES (Accessed by other components)
169 * ============================================================
170 */
171
172 /**
173 * Reference to the main calendar grid UI component.
174 * CalendarView is loaded dynamically at runtime.
175 */
176 public CalendarView: any;
177
178 /**
179 * Manages all calendar event data (fetching, filtering, caching).
180 * Connected to Evolution Data Server (EDS) via DBus.
181 */
182 public eventManager: EventManager;
183
184 /**
185 * Displays events in a list/agenda format (left sidebar).
186 */
187 public eventListView: EventListView;
188
189 /**
190 * Pure business logic for date calculations and holiday detection.
191 * No UI dependencies, no I/O operations.
192 */
193 public CalendarLogic: CalendarLogic;
194
195 /**
196 * The popup menu that contains the entire calendar UI.
197 * TypeScript declaration - actual initialization is in constructor.
198 */
199 declare public menu: any;
200
201 /* ============================================================
202 * PRIVATE PROPERTIES (Internal implementation)
203 * ============================================================
204 */
205
206 /**
207 * Manages the popup menu lifecycle and state.
208 */
209 private menuManager: any;
210
211 /**
212 * Handles persistence of user settings (panel icon, formats, etc.).
213 */
214 private settings: any;
215
216 /**
217 * ID of the periodic update timer. Used for cleanup.
218 */
219 private _updateId: number = 0;
220
221 /**
222 * Unique identifier for this applet instance.
223 * Used for settings keys and hotkey registration.
224 */
225 private uuid: string;
226
227 /* ============================================================
228 * UI ELEMENTS (Header Section)
229 * ============================================================
230 *
231 * The header displays current date information and acts as a
232 * "home" button to return to today's date.
233 */
234
235 /**
236 * Main vertical container for the entire popup UI.
237 */
238 private _mainBox: any;
239
240 /**
241 * Horizontal layout bridge that holds left (events) and right (calendar) columns.
242 */
243 private _contentLayout: any;
244
245 /**
246 * Day of week label (e.g., "Monday").
247 */
248 private _dayLabel: any;
249
250 /**
251 * Date label (e.g., "1. January 2026").
252 */
253 private _dateLabel: any;
254
255 /**
256 * Holiday label (shows holiday names when applicable).
257 */
258 private _holidayLabel: any;
259
260 /* ============================================================
261 * SETTINGS PROPERTIES (Bound to UI settings)
262 * ============================================================
263 *
264 * These properties are automatically synchronized with the
265 * Cinnamon settings system via bind() calls.
266 */
267
268 /**
269 * Whether to show the calendar icon in the panel.
270 */
271 public showIcon: boolean = false;
272
273 /**
274 * Whether to show the event list sidebar.
275 */
276 public showEvents: boolean = true;
277
278 /**
279 * Whether to display ISO week numbers in the month grid.
280 */
281 public showWeekNumbers: boolean = false;
282
283 /**
284 * Whether to use custom date/time formats.
285 */
286 public useCustomFormat: boolean = false;
287
288 /**
289 * Custom format string for panel label (uses GLib.DateTime format).
290 */
291 public customFormat: string = "";
292
293 /**
294 * Custom format string for panel tooltip.
295 */
296 public customTooltipFormat: string = "";
297
298 /**
299 * Global hotkey to open the calendar popup.
300 */
301 public keyOpen: string = "";
302
303 /* ============================================================
304 * CONSTRUCTOR
305 * ============================================================
306 *
307 * Called by Cinnamon when the applet is loaded into the panel.
308 * Initializes ALL components and builds the complete UI hierarchy.
309 *
310 * The constructor is organized in clear phases:
311 * 1. Backend initialization (settings, managers, logic)
312 * 2. UI construction (layout, components, wiring)
313 * 3. Signal connections and final setup
314 */
315 /**
316 * @brief Constructs the UniversalCalendarApplet
317 *
318 * @param metadata Applet metadata from Cinnamon
319 * @param orientation Panel orientation (horizontal/vertical)
320 * @param panelHeight Height of the panel in pixels
321 * @param instanceId Unique instance identifier
322 *
323 * @note Called automatically by Cinnamon when the applet is loaded.
324 * The constructor is organized in clear phases:
325 * 1. Backend initialization
326 * 2. UI construction
327 * 3. Signal connections
328 */
329
330 constructor(metadata: any, orientation: any, panel_height: number, instance_id: number) {
331 // Call parent constructor (TextIconApplet)
332 super(orientation, panel_height, instance_id);
333
334 // Store applet identifier for settings and hotkeys
335 this.uuid = metadata.uuid;
336
337 // Initialize translation system
338 setupLocalization(this.uuid, metadata.path);
339
340 try {
341 /* ====================================================
342 * PHASE 1: BACKEND INITIALIZATION
343 * ==================================================== */
344
345 // 1.1 Settings system - persists user preferences
346 this.settings = new Settings.AppletSettings(this, this.uuid, instance_id);
347
348 // 1.2 Menu manager - handles popup menu lifecycle
349 this.menuManager = new PopupMenu.PopupMenuManager(this);
350
351 // 1.3 Core business components
352 this.eventManager = new EventManager();
353 this.eventListView = new EventListView();
354 this.CalendarLogic = new CalendarLogic(metadata.path);
355
356 // 1.4 Dynamic component loading
357 // CalendarView is loaded from a separate file at runtime
358 const CalendarModule = FileUtils.requireModule(metadata.path + '/CalendarView');
359
360 /* ====================================================
361 * PHASE 2: SETTINGS BINDING
362 * ====================================================
363 *
364 * Connect settings UI to internal properties.
365 * When a setting changes, the corresponding callback is triggered.
366 */
367
368 this.settings.bind("show-icon", "showIcon", this.on_settings_changed);
369 this.settings.bind("show-events", "showEvents", this.on_settings_changed);
370 this.settings.bind("show-week-numbers", "showWeekNumbers", this.on_settings_changed);
371 this.settings.bind("use-custom-format", "useCustomFormat", this.on_settings_changed);
372 this.settings.bind("custom-format", "customFormat", this.on_settings_changed);
373 this.settings.bind("custom-tooltip-format", "customTooltipFormat", this.on_settings_changed);
374 this.settings.bind("keyOpen", "keyOpen", this.on_hotkey_changed);
375
376 /* ====================================================
377 * PHASE 3: POPUP MENU CONSTRUCTION
378 * ==================================================== */
379
380 // Create the popup menu that will host our calendar UI
381 this.menu = new Applet.AppletPopupMenu(this, orientation);
382 this.menuManager.addMenu(this.menu);
383
384 /* ====================================================
385 * PHASE 4: UI CONSTRUCTION
386 * ====================================================
387 *
388 * The UI is built as a hierarchical tree of St widgets.
389 * This is the most complex part of the constructor.
390 */
391
392 // 4.1 Main vertical container (root of our UI)
393 this._mainBox = new St.BoxLayout({
394 vertical: true,
395 style_class: 'calendar-main-box'
396 });
397
398 /**
399 * HEADER SECTION
400 * --------------
401 * Displays current day/date and acts as a "Home" button.
402 * Clicking it returns to today's date in the calendar.
403 *
404 * Visual structure:
405 * ┌─────────────────────────────┐
406 * │ Monday │ ← Day label
407 * │ 1. January 2026 │ ← Date label
408 * │ New Year's Day │ ← Holiday (optional)
409 * └─────────────────────────────┘
410 */
411
412 let headerBox = new St.BoxLayout({
413 vertical: true,
414 style_class: 'calendar-today-home-button',
415 reactive: true // Makes it clickable
416 });
417
418 // Click handler: Return to today's date
419 headerBox.connect("button-release-event", () => {
420 this.CalendarView.resetToToday();
421 this.setHeaderDate(new Date());
422 });
423
424 // Create header labels
425 this._dayLabel = new St.Label({ style_class: 'calendar-today-day-label' });
426 this._dateLabel = new St.Label({ style_class: 'calendar-today-date-label' });
427 this._holidayLabel = new St.Label({ style_class: 'calendar-today-holiday' });
428
429 // Add labels to header
430 headerBox.add_actor(this._dayLabel);
431 headerBox.add_actor(this._dateLabel);
432 headerBox.add_actor(this._holidayLabel);
433
434 /**
435 * CALENDAR GRID
436 * -------------
437 * The main calendar component (month/year/day views).
438 * Loaded dynamically from CalendarView module.
439 */
440 this.CalendarView = new CalendarModule.CalendarView(this);
441
442 /**
443 * SIGNAL CONNECTION: Event List → Calendar Navigation
444 * ----------------------------------------------------
445 * When a user clicks an event in the list view,
446 * the calendar should jump to that event's date.
447 *
448 * This creates navigation flow between components.
449 */
450 this.eventListView.connect('event-clicked', (actor: any, ev: any) => {
451 if (ev && ev.start) {
452 // 1. Jump calendar to the event's date
453 this.CalendarView.jumpToDate(ev.start);
454
455 // 2. Update header to show the event's date
456 this.setHeaderDate(ev.start);
457 }
458 });
459
460 /**
461 * FOOTER SECTION
462 * ---------------
463 * System management buttons at the bottom.
464 */
465 let footerBox = new St.BoxLayout({ style_class: 'calendar-footer' });
466
467 // Button: Open Cinnamon's date/time settings
468 let settingsBtn = new St.Button({
469 label: _("Date and Time Settings"),
470 style_class: 'calendar-footer-button',
471 x_expand: true
472 });
473 settingsBtn.connect("clicked", () => {
474 this.menu.close();
475 Util.spawnCommandLine("cinnamon-settings calendar");
476 });
477
478 // Button: Open calendar management
479 let calendarBtn = new St.Button({
480 label: _("Manage Calendars"),
481 style_class: 'calendar-footer-button',
482 x_expand: true
483 });
484 calendarBtn.connect("clicked", () => {
485 this.menu.close();
486
487 const currentDate = this.CalendarView.getCurrentlyDisplayedDate();
488 const epoch = Math.floor(currentDate.getTime() / 1000);
489
490 // Try to open via calendar:// URI (XDG standard)
491 try {
492 Util.spawnCommandLine(`xdg-open calendar:///?startdate=${epoch}`);
493 } catch (e) {
494 // Fallback to GNOME Calendar if URI fails
495 Util.spawnCommandLine(`gnome-calendar --date=${epoch}`);
496 }
497 });
498
499 // Add buttons to footer
500 footerBox.add_actor(settingsBtn);
501 footerBox.add_actor(calendarBtn);
502
503 /**
504 * LAYOUT COMPOSITION
505 * ------------------
506 * Assemble the two-column layout:
507 *
508 * ┌─────────────────────────────────────────────┐
509 * │ EventListView │ Header + Calendar + Footer │
510 * │ (Left Column) │ (Right Column) │
511 * └─────────────────────────────────────────────┘
512 */
513
514 // 1. Create right column (traditional calendar view)
515 let rightColumn = new St.BoxLayout({
516 vertical: true,
517 style_class: 'calendar-right-column'
518 });
519 rightColumn.add_actor(headerBox);
520 rightColumn.add_actor(this.CalendarView.actor);
521 rightColumn.add_actor(footerBox);
522
523 // 2. Create horizontal bridge container
524 this._contentLayout = new St.BoxLayout({
525 vertical: false, // Side-by-side layout
526 style_class: 'calendar-content-layout'
527 });
528
529 // 3. Add left wing (events) and right column (calendar)
530 this._contentLayout.add_actor(this.eventListView.actor);
531 this._contentLayout.add_actor(rightColumn);
532
533 // 4. Final assembly: Add everything to main container
534 this._mainBox.add_actor(this._contentLayout);
535
536 // 5. Add main container to popup menu
537 this.menu.addActor(this._mainBox);
538
539 /* ====================================================
540 * PHASE 5: INITIALIZATION AND SIGNAL SETUP
541 * ==================================================== */
542
543 // Apply initial settings
544 this.on_settings_changed();
545 this.on_hotkey_changed();
546
547 /**
548 * MENU OPEN/CLOSE HANDLING
549 * -------------------------
550 * When menu opens:
551 * 1. Refresh calendar display
552 * 2. Update header to current date
553 * 3. Focus calendar for keyboard navigation
554 */
555 this.menu.connect("open-state-changed", (menu: any, isOpen: boolean) => {
556 if (isOpen) {
557 this.CalendarView.render();
558 this.setHeaderDate(new Date());
559
560 // Small delay to ensure UI is ready before focusing
561 GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => {
562 this.CalendarView.actor.grab_key_focus();
563 return false;
564 });
565 }
566 });
567
568 // Initial panel label/tooltip update
569 this.update_label_and_tooltip();
570
571 // Start periodic updates (every 10 seconds)
572 this._updateId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 10, () => {
573 this.update_label_and_tooltip();
574 return true; // Continue timer
575 });
576
577 } catch (e) {
578 // Log critical initialization failures
579 global.log(`[${this.uuid}] CRITICAL: Initialization failed: ${e}`);
580 }
581 }
582
583 /* ============================================================
584 * SETTINGS CHANGE HANDLER
585 * ============================================================
586 *
587 * Called automatically when ANY bound setting changes.
588 * Updates UI elements to reflect new settings.
589 */
590
591 on_settings_changed() {
592 // Toggle panel icon visibility
593 if (this.showIcon) {
594 this.set_applet_icon_name("office-calendar");
595 if (this._applet_icon_box) this._applet_icon_box.show();
596 } else {
597 this._hide_icon();
598 }
599
600 // Toggle event list visibility (left sidebar)
601 if (this.eventListView) {
602 if (this.showEvents) {
603 this.eventListView.actor.show();
604 } else {
605 this.eventListView.actor.hide();
606 }
607 }
608
609 // Update panel label and tooltip
610 this.update_label_and_tooltip();
611
612 // If menu is open, re-render to reflect format changes
613 if (this.menu && this.menu.isOpen) {
614 this.CalendarView.render();
615 }
616 }
617
618 /* ============================================================
619 * HELPER: HIDE PANEL ICON
620 * ============================================================
621 *
622 * Cleanly hides the icon from the panel.
623 * Different Cinnamon versions handle empty icons differently.
624 */
625
626 _hide_icon() {
627 this.set_applet_icon_name("");
628 if (this._applet_icon_box) {
629 this._applet_icon_box.hide();
630 }
631 }
632
633 /* ============================================================
634 * PANEL CLICK HANDLER
635 * ============================================================
636 *
637 * Called by Cinnamon when user clicks the applet in the panel.
638 */
639 /**
640 * @brief Handles panel icon clicks
641 *
642 * @param event The click event from Cinnamon
643 *
644 * @note Called automatically by Cinnamon when user clicks the panel icon.
645 * Toggles the popup menu and refreshes events if opening.
646 */
647
648 on_applet_clicked(event: any): void {
649 // Refresh events if opening the menu
650 if (!this.menu.isOpen) {
651 this.eventManager.refresh();
652 }
653 // Toggle menu open/close
654 this.menu.toggle();
655 }
656
657 /* ============================================================
658 * HOTKEY CHANGE HANDLER
659 * ============================================================
660 *
661 * Updates global keyboard shortcut when setting changes.
662 */
663
664 on_hotkey_changed() {
665 // Remove old hotkey
666 Main.keybindingManager.removeHotKey(`${this.uuid}-open`);
667
668 // Register new hotkey if set
669 if (this.keyOpen) {
670 Main.keybindingManager.addHotKey(`${this.uuid}-open`, this.keyOpen, () => {
671 this.on_applet_clicked(null);
672 });
673 }
674 }
675
676 /* ============================================================
677 * PANEL LABEL AND TOOLTIP UPDATER
678 * ============================================================
679 *
680 * Updates the text shown in the Cinnamon panel and its tooltip.
681 * Runs periodically (every 10 seconds) to keep time accurate.
682 */
683
684 update_label_and_tooltip() {
685 const now = new Date();
686 const gNow = GLib.DateTime.new_now_local();
687
688 // Panel label (time display)
689 let timeLabel = this.useCustomFormat
690 ? gNow.format(this.customFormat)
691 : now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
692
693 // Tooltip (date display)
694 let dateTooltip = this.useCustomFormat
695 ? gNow.format(this.customTooltipFormat)
696 : now.toLocaleDateString([], {
697 weekday: 'long',
698 year: 'numeric',
699 month: 'long',
700 day: 'numeric'
701 });
702
703 this.set_applet_label(timeLabel || "");
704 this.set_applet_tooltip(dateTooltip || "");
705 }
706
707 /* ============================================================
708 * HEADER DATE UPDATER
709 * ============================================================
710 *
711 * Updates the header section in the popup menu.
712 * Shows day, date, and holiday information.
713 *
714 * @param date - The date to display in the header
715 */
716
717 public setHeaderDate(date: Date) {
718 if (!this._dayLabel || !this.CalendarView) return;
719
720 const gDate = GLib.DateTime.new_from_unix_local(date.getTime() / 1000);
721
722 // Format: "Monday"
723 this._dayLabel.set_text(gDate.format("%A"));
724
725 // Format: "1. January 2026"
726 this._dateLabel.set_text(gDate.format("%e. %B %Y"));
727
728 // Check for holidays
729 const tagInfo = this.CalendarView.getHolidayForDate(date);
730 if (tagInfo && tagInfo.beschreibung) {
731 this._holidayLabel.set_text(tagInfo.beschreibung);
732 this._holidayLabel.show();
733 } else {
734 this._holidayLabel.hide();
735 }
736 }
737
738 /* ============================================================
739 * CLEANUP HANDLER
740 * ============================================================
741 *
742 * Called when applet is removed from panel or Cinnamon restarts.
743 * Essential to prevent memory leaks and dangling resources.
744 */
745
746 on_applet_removed_from_panel() {
747 // Remove global hotkey
748 Main.keybindingManager.removeHotKey(`${this.uuid}-open`);
749
750 // Stop periodic update timer
751 if (this._updateId > 0) {
752 GLib.source_remove(this._updateId);
753 }
754
755 // Destroy menu and all UI elements
756 this.menu.destroy();
757 }
758
759 /* ============================================================
760 * ICS FILE IMPORT DIALOG
761 * ============================================================
762 *
763 * Opens a GTK file chooser to import .ics calendar files.
764 * Currently not connected in UI (commented out in CalendarView).
765 *
766 * TODO: This feature is disabled due to EDS import limitations.
767 */
768
769 private _openICSFileChooser(): void {
770 const dialog = new Gtk.FileChooserDialog({
771 title: _("Import Calendar (.ics)"),
772 action: Gtk.FileChooserAction.OPEN,
773 modal: true,
774 });
775
776 dialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL);
777 dialog.add_button(_("Import"), Gtk.ResponseType.OK);
778
779 const filter = new Gtk.FileFilter();
780 filter.set_name("iCalendar (*.ics)");
781 filter.add_pattern("*.ics");
782 dialog.add_filter(filter);
783
784 dialog.connect("response", (_dlg: any, response: number) => {
785 if (response === Gtk.ResponseType.OK) {
786 const file = dialog.get_file();
787 if (file) {
788 const path = file.get_path();
789 if (path) {
790 this.eventManager.importICSFile(path)
791 .catch(e => {
792 global.logError(
793 `${this.uuid}: ICS import failed: ${e}`
794 );
795 });
796 }
797 }
798 }
799 dialog.destroy();
800 });
801
802 dialog.show_all();
803 }
804}
805
806/* ================================================================
807 * CINNAMON ENTRY POINT
808 * ================================================================
809 *
810 * Cinnamon calls this function to create the applet instance.
811 * Must be named 'main' exactly.
812 */
813
814function main(metadata: any, orientation: any, panel_height: number, instance_id: number) {
815 try {
816 return new UniversalCalendarApplet(metadata, orientation, panel_height, instance_id);
817 } catch (e) {
818 // Log initialization errors to Cinnamon's global log
819 if (typeof global !== 'undefined') {
820 global.log(metadata.uuid + " CRITICAL: Initialization error: " + e);
821 }
822 return null;
823 }
824}
825
826/* ================================================================
827 * GLOBAL EXPORT (CINNAMON RUNTIME REQUIREMENT)
828 * ================================================================
829 *
830 * CRITICAL: Cinnamon loads applets by evaluating JS files.
831 * There is NO module system at runtime - everything must be global.
832 *
833 * This dual export pattern supports:
834 * 1. TypeScript/development environment (exports)
835 * 2. Cinnamon production runtime (global assignment)
836 */
837
838if (typeof global !== 'undefined') {
839 global.main = main;
840 (global as any).main = main;
841 if (typeof Applet !== 'undefined') {
842 global.Applet = Applet;
843 (global as any).Applet = Applet;
844 }
845}
846
847/* ================================================================
848 * TODOs (DOCUMENTATION ONLY - NO CODE CHANGES)
849 * ================================================================
850 *
851 * TODO: Add lazy loading for CalendarView to improve startup performance.
852 *
853 * TODO: Implement proper error boundaries for component initialization failures.
854 *
855 * TODO: Add comprehensive keyboard navigation between EventListView and CalendarView.
856 *
857 * TODO: Consider extracting the two-column layout into a reusable LayoutManager class.
858 *
859 * TODO: Add support for calendar color theme synchronization with system theme.
860 *
861 * TODO: Implement drag-and-drop event creation in month view.
862 *
863 * TODO: Add export functionality (current month events to .ics).
864 */