import Clutter from 'gi://Clutter'; import GObject from 'gi://GObject'; import St from 'gi://St'; import * as D from './dates.js'; import {escapeMarkup} from './markdown.js'; import {contextMenu, dropdown, openDatePicker, openTimePicker, Popover, scrollBox, textButton} from './widgets.js'; // '17.09. 14:00', '17.09.' oder '' bei allgemeinen Todos. export function todoWhen(todo) { if (!todo.date) return ''; return todo.time ? `${D.formatDayMonth(todo.date)} ${todo.time}` : D.formatDayMonth(todo.date); } // Erste Zeile des Textes für Listen. export function todoTitle(todo) { return todo.title.split('\n')[0]; } // Popover am Mauszeiger mit Textfeld sowie Auswahl für Datum und Uhrzeit. Enter speichert. export function editTodo(store, todo) { const popover = new Popover(); popover.addLabel(todo.id ? 'Todo bearbeiten' : 'Neues Todo', 'popover-title'); const text = popover.addEntry(todo.title ?? '', 'Was ist zu tun?'); let date = todo.date ?? null; let time = todo.time ?? null; const row = new St.BoxLayout({style_class: 'popover-row'}); const dateButton = dropdown('', b => openDatePicker(b, date, picked => { date = picked; if (!date) time = null; updateLabels(); }), text.clutter_text); const timeButton = dropdown('', b => openTimePicker(b, time, picked => { time = picked; // Eine Uhrzeit ohne Datum meint heute. if (time && !date) date = D.today(); updateLabels(); }), text.clutter_text); const updateLabels = () => { dateButton.setLabel(date ? D.formatDate(date) : 'Kein Datum'); timeButton.setLabel(time ?? 'Keine Uhrzeit'); }; updateLabels(); row.add_child(dateButton); row.add_child(timeButton); popover.add(row); popover.setSave(() => { const title = text.get_text().trim(); if (!title) return 'Text fehlt'; store.saveTodo({...todo, title, date, time}); }); popover.open(text.clutter_text); return popover; } export function todoMenu(store, todo) { contextMenu([{label: 'Löschen', onSelect: () => store.removeTodo(todo.id)}]); } // Gilt überall, wo ein Todo steht: links bearbeiten, Mitte abhaken, rechts Menü. export function todoClick(store, todo, mouseButton) { if (mouseButton === 2) store.toggleTodo(todo.id); else if (mouseButton === 3) todoMenu(store, todo); else editTodo(store, todo); } // Zeile mit Häkchen, Titel und Termin. Links bearbeitet, Mitte hakt ab, rechts öffnet das Menü. export function todoRow(store, todo) { const row = new St.BoxLayout({style_class: 'todo-row', x_expand: true}); const check = new St.Button({ style_class: 'todo-check', can_focus: true, child: new St.Icon({icon_name: todo.done ? 'checkbox-checked-symbolic' : 'checkbox-symbolic', icon_size: 16}), }); check.connect('clicked', () => store.toggleTodo(todo.id)); row.add_child(check); const title = textButton(todoTitle(todo), (_b, mouseButton) => todoClick(store, todo, mouseButton), todo.done ? 'todo-title done' : 'todo-title'); if (todo.done) title.child.clutter_text.set_markup(`${escapeMarkup(todoTitle(todo))}`); row.add_child(title); const when = todoWhen(todo); if (when) { const label = new St.Label({text: when, style_class: 'todo-when', y_align: Clutter.ActorAlign.CENTER}); if (!todo.done && todo.date < D.today()) label.add_style_class_name('overdue'); row.add_child(label); } return row; } export const TodoPanel = GObject.registerClass( class TodoPanel extends St.BoxLayout { _init(store) { super._init({vertical: true, style_class: 'desktop-panel todos', reactive: true, x_expand: true, y_expand: true}); this._store = store; const [scroll, list] = scrollBox('todo-scroll'); this._list = list; this.add_child(scroll); // Rechtsklick auf die freie Fläche this.connect('button-press-event', (_actor, event) => { if (event.get_button() !== Clutter.BUTTON_SECONDARY) return Clutter.EVENT_PROPAGATE; contextMenu([{label: 'Erstellen', onSelect: () => editTodo(store, {})}]); return Clutter.EVENT_STOP; }); store.onChange(() => this._render()); this._render(); } _render() { this._list.destroy_all_children(); const todos = this._store.sortedTodos(); if (!todos.length) this._list.add_child(new St.Label({text: 'Keine Todos', style_class: 'empty-hint'})); for (const todo of todos) this._list.add_child(todoRow(this._store, todo)); } });