init
This commit is contained in:
129
desktop@marha.de/todos.js
Normal file
129
desktop@marha.de/todos.js
Normal file
@@ -0,0 +1,129 @@
|
||||
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 mit Textfeld sowie Auswahl für Datum und Uhrzeit. Enter speichert.
|
||||
export function editTodo(store, todo, anchor) {
|
||||
const popover = new Popover(anchor);
|
||||
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)}]);
|
||||
}
|
||||
|
||||
// Zeile mit Häkchen, Titel und Termin. Klick bearbeitet, Rechtsklick ö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) => {
|
||||
if (mouseButton === 3)
|
||||
todoMenu(store, todo);
|
||||
else
|
||||
editTodo(store, todo, b);
|
||||
}, todo.done ? 'todo-title done' : 'todo-title');
|
||||
if (todo.done)
|
||||
title.child.clutter_text.set_markup(`<s>${escapeMarkup(todoTitle(todo))}</s>`);
|
||||
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;
|
||||
const [x, y] = global.get_pointer();
|
||||
contextMenu([{label: 'Erstellen', onSelect: () => editTodo(store, {}, {x, y})}]);
|
||||
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));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user