Gramplets.de - dsblank/gramps-connect GitHub Wiki
🌐 English · Español · Français · Nederlands · 简体中文
Gramplets sind das Add-on-System von Gramps Connect: kleine
Python-Programme, die den Stammbaum abfragen und ein Ergebnis darstellen
— eine Tabelle, ein Diagramm, reinen Text oder HTML. Es gibt sie in zwei
Arten: Gramplets, die Sie aus dem Menü Gramplets in einem
eigenen Fenster ausführen, und Ansichts-Gramplets, die in einem
Bereich unter einer Liste sitzen (Person, Familie oder jeder andere
Objekttyp) und dem folgen, was Sie dort auswählen. Was sie ungewöhnlich
macht, ist wo sie laufen: jedes ist echtes Python,
das direkt im eigenen Browser-Tab unter Pyodide
ausgeführt wird (nach WebAssembly kompiliertes CPython), gegen lokal
gebaute Wheels von Gramps' eigenem Datenmodell gramps.gen.lib. Nichts
wird auf dem eigenen Rechner installiert, und nichts läuft auf einem
Server — siehe
Architektur.
Das bedeutet auch, dass jedes Gramplet bearbeitbar ist. Eines aus dem Gramplet-Editor der App heraus öffnen, und man sieht dessen echten Quellcode (und kann ihn ändern) — keine Blackbox hinter einem Einstellungsbereich. Wie ein Diagramm aussieht ändern, wonach eine Abfrage sucht anpassen, oder eine ganz neue von Grund auf schreiben, alles ohne die App zu verlassen.
Jeder, der im Stammbaum angemeldet ist, kann dessen Gramplets ausführen, aus dem Gramplets-Menü oder im Bereich „Ansichts-Gramplets“ einer Liste. Nur der Eigentümer des Stammbaums oder ein Site-Admin kann sie schreiben: eines anlegen, den Code eines bearbeiten oder eines über den Gramplet-Store installieren, aktualisieren oder entfernen. Für alle anderen erscheinen der Store, Neues Gramplet… und die Bearbeiten-Stifte gar nicht.
| Rolle | Gramplets ausführen | Gramplets schreiben / installieren |
|---|---|---|
| Gast | ✓ (außer private) | — |
| Mitglied | ✓ | — |
| Mitwirkender | ✓ | — |
| Bearbeiter | ✓ | — |
| Eigentümer | ✓ | ✓ |
| Admin | ✓ | ✓ |
Ein Gramplet zu schreiben erfordert mehr als die üblichen Rechte eines Bearbeiters, weil sein Code im Browser aller läuft, die es verwenden — eine Änderung daran wirkt sich also für alle auf den Stammbaum aus, nicht nur auf die eigenen Bearbeitungen. Ein Gramplet wird als gewöhnliches Medienobjekt mit dem Tag Gramplet gespeichert, daher gelten die üblichen Datenschutzregeln: Ist es als privat markiert, sehen Gäste es nicht.
Dasselbe gilt für Datenänderungen erlauben (siehe Daten ändern): Nur ein Eigentümer oder Admin kann es für ein Gramplet einschalten. Jeder kann einen Probelauf eines solchen Gramplets machen, der nichts speichert; das Speichern seiner Änderungen erfordert Bearbeiter-Rechte oder höher (die eigenen Hinzufügen-, Bearbeiten- und Löschrechte des Servers), daher ist die Schaltfläche Ausführen des Fensters für Gäste, Mitglieder und Mitwirkende deaktiviert.
Der Gramplet-Store ist ein durchsuchbarer Katalog innerhalb der App —
geöffnet über Gramplets → Gramplet-Store…. Er hat zwei Reiter,
Gramplets und Ansichts-Gramplets; jeden Eintrag mit einem Klick
installieren, aktualisieren oder entfernen. Ein installiertes Gramplet
erscheint sofort im Gramplets-Menü (nach seiner Kategorie gruppiert);
ein installiertes Ansichts-Gramplet wird einer Liste über das + im
Bereich „Ansichts-Gramplets“ dieser Liste hinzugefügt. Er wird unabhängig von den eigenen Releases von Gramps
Connect veröffentlicht — der Katalog ist statischer Inhalt
(gramplet-store/
im Repository), zur Laufzeit von der laufenden App abgerufen, nicht in
den App-Build eingebunden, sodass neue Gramplets im Store erscheinen
können, ohne dass jemand Gramps Connect selbst aktualisiert.
Beispiele, die bereits im Katalog stehen, sind diese Gramplets, die aus dem Gramplets-Menü laufen:
| Gramplet | Was es tut |
|---|---|
| Age at Death by Decade | Diagramm des mittleren Sterbealters (Median) für jedes Jahrzehnt der Geburt oder des Todes |
| Mark Unsourced People Private | Markiert alle Personen ganz ohne Quelle als privat — ändert Daten, daher ist das Öffnen ein Probelauf, der auflistet, wer sich ändern würde, und Ausführen speichert es (siehe Daten ändern) |
| People Without Sources | Alle ohne Zitat an sich selbst oder an einem ihrer Ereignisse (ein Durchlauf über den ganzen Stammbaum mit Fortschrittsbalken) |
| Possible Duplicate People | Gleichnamige Personen, die innerhalb weniger Jahre voneinander geboren sind, zum Prüfen und Zusammenführen |
| Tag People | Fügt allen im aktuellen Personenfilter ein Tag hinzu, das Sie aus einer Liste wählen — ändert Daten, daher ist es ein Probelauf, bis Sie auf Ausführen klicken |
und diese Ansichts-Gramplets:
| Ansichts-Gramplet | Was es tut |
|---|---|
| Age-at-Death Histogram | Interaktives Plotly-Histogramm des Sterbealters über den Stammbaum (oder die aktuell gefilterte Liste) |
| All Relationships | Verwandtschaftsberechnungen über den ganzen Stammbaum |
| Attributes / Backlinks / Children / Citations / Coordinates / Events / Gallery / Notes | Detailbereiche im Stil der Desktop-Gramplets für den ausgewählten Datensatz |
| Born In | Nachschlagen nach Geburtsort |
| Crossing Relationships | Findet Verwandtschaften, die Generationen auf ungewöhnliche Weise kreuzen |
| GOQL Query | Eine rohe GOQL-where-Bedingung eintippen und Feldausdrücke als Spalten wählen |
| Hello Table | Das minimale Einstiegsbeispiel |
| People Search | Eine Live-Suche nach Personen während der Eingabe, nach Name, Geschlecht und Sterbedatum |
| Phonebook Surname Sort | Akzent-unabhängige Nachnamensortierung (z. B. sortiert „Müller“ neben „Muller“) |
| Relationship / Relationships | Wie zwei ausgewählte Personen verwandt sind |
| Selected Record | Reagiert auf den gerade geöffneten Datensatz |
| To-Do / Todo Tracker | Verfolgt Forschungs-To-dos über den ganzen Stammbaum |
| Tree Statistics | Ein Dashboard mit Datensatzzahlen und abgeleiteten Prozentwerten |
In jeder Listenansicht (Personen, Familien, Ereignisse, …) erscheinen die Ansichts-Gramplets, die Sie dieser Ansicht hinzugefügt haben, als Reiter in einem einklappbaren Bereich Ansichts-Gramplets unter der Tabelle. Der Bereich ist anfangs eingeklappt, und ein Ansichts-Gramplet läuft nur, solange er geöffnet ist: Beim Ausklappen wird der ausgewählte Reiter ausgeführt. Der erste Lauf in einer Sitzung lädt außerdem die Python-Laufzeitumgebung im Browser und kann deshalb ein paar Sekunden dauern; spätere Läufe sind schnell. Ein eingeklappter Bereich kostet nichts, da Python erst beim Öffnen geladen wird.
Ein Gramplet ist (anders als ein Ansichts-Gramplet) an keine Liste gebunden. Wählen Sie eines aus dem Menü Gramplets — nach Kategorien in Untermenüs gruppiert, sobald es mehr als eine Kategorie gibt —, und es öffnet sich in einem schwebenden Fenster:
- Es läuft beim Öffnen. Ein Gramplet, das zuerst eine Eingabe braucht — einen Nachnamen, eine Anzahl von Generationen —, zeichnet seine eigenen Felder und eine Schaltfläche Ausführen, mit denselben Widgets, die Ansichts-Gramplets verwenden.
- Gramplets, die Daten ändern (im Menü mit ✎ markiert), machen beim Öffnen einen Probelauf und haben die Schaltflächen Probelauf / Ausführen sowie Rückgängig — siehe Daten ändern.
- Es sieht die Liste dahinter. Ist in der Hauptansicht eine Liste geöffnet, sieht das Gramplet deren ausgewählten Datensatz und deren aktuellen Filter, so wie sie beim Start des Laufs waren; wechselt man danach woandershin, ändert das einen laufenden Lauf nicht.
- Fortschritt und Abbrechen. Ein längeres Gramplet zeigt einen Fortschrittsbalken, während es arbeitet; Abbrechen hält es sofort an und behält, was es bis dahin erzeugt hat.
- Währenddessen weiterarbeiten. Ein minimiertes Fenster (▾) wird zu einem kleinen Chip am unteren Bildschirmrand, der anzeigt, wie weit es ist; der Lauf geht weiter. Wenn ein minimiertes Gramplet fertig ist, erhalten Sie eine Benachrichtigung mit einer Schaltfläche Anzeigen. Mehrere Gramplets können gleichzeitig laufen (bis zu fünf Fenster), jedes unabhängig, und sie bleiben offen, während Sie zwischen Ansichten wechseln.
- Verschieben, Größe ändern, maximieren. Ein Fenster an seiner Titelleiste ziehen, die Größe an seiner Ecke ändern oder es maximieren (⤢ oder Doppelklick auf den Titel). Jedes Gramplet merkt sich Größe und Position seines Fensters in Ihrem Browser.
- Bearbeiten. Für den Eigentümer des Stammbaums oder einen Site-Admin (siehe Wer Gramplets verwenden kann) hat die Titelleiste des Fensters einen Stift (✎), der den Gramplet-Editor öffnet; Speichern startet das Fenster mit Ihren Änderungen neu.
- Schließen. Um ein Gramplet noch einmal von vorn auszuführen, sein Fenster schließen und es erneut aus dem Menü wählen. Das Schließen eines noch laufenden Fensters fragt zuerst nach, ebenso das Verlassen oder Neuladen der Seite — das Schließen des Browser-Tabs beendet jeden Lauf.
Ergebnisse werden nur im Fenster angezeigt; sie werden noch nirgends gespeichert.
Der Code eines Gramplets läuft in einer Sandbox mit bereits verfügbaren
Funktionen und Objekten — keine Imports nötig, um an den Stammbaum zu
kommen. Das Verzeichnis
gramplet_examples/
im Repository ist ein durchnummerierter Rundgang durch die API, vom
Einfachsten zum Fortgeschrittensten; dieselbe Referenz ist auch aus der
App selbst heraus verfügbar, über die Hilfe-Schaltfläche (i)
„Ein Gramplet schreiben“ im Gramplet-Editor.
matches = people(
"gender == Person.MALE and birth.date.sortval >= Date('Jan 1, 1900')",
limit=25,
)
columns("Person")
for person in matches:
row(person)-
people(where=None, what=None, order=None, limit=50)ruft Personen ab, die auf eine Abfrage passen, als vollständige Datensätze, in einem Aufruf — oder, mitwhat=["gramps_id", "primary_name"], nur diese Felder — und es gibtfamilies()/events()/places()/repositories()/sources()/citations()/media()/notes()/tags()für jeden anderen Objekttyp, dieselbe Signatur, nur eine andere Tabelle. -
where=wird in GOQL geschrieben — exakt derselben Syntax wie das Suchfeld jeder Listenansicht, einschließlich des Reichens über Beziehungen hinweg (birth.date.sortval), selbst wenn das gefilterte Feld nicht eines der angezeigten ist. - Kein
awaitnötig, obwohl das im Hintergrund ein echter Netzwerkaufruf ist — es wird automatisch eingefügt. -
row(*values)fügt eine Zeile zur Tabelle hinzu. Ein ganzes Person-/Event-/Place-/…-Objekt zu übergeben (statt eines von Hand ausgewählten Felds davon) stellt es als anklickbaren Link dar, der bereits den vollen Namen/Titel und die Gramps-ID zeigt — ein Klick darauf öffnet ein Popup, um diesen Datensatz in Liste, Karte, Diagramm oder Zeitleiste anzusehen. -
name_of(person)ist der Name einer Person als Text — Vorname, dann Nachnamen („Root Ancestor“), so wierow()sie beschriftet — für eine Überschrift oder einen Satz. Nimmt auch einen ihrer Namen an, z. B.name_of(person.alternate_names[0]).
Jeder von people()/families()/usw. zurückgegebene Datensatz ist ein
Gramps-„DataDict“ — ein schlichtes Dict der Felder des Objekts, aber mit
Punktzugriff obendrauf: person.primary_name.first_name ist derselbe
Wert wie person["primary_name"]["first_name"]. Darauf zurückgreifen,
wann immer etwas aus einem Feld berechnet werden soll oder etwas
gezeigt werden soll, das die eigene Darstellung von row() nicht
zeigt. row(), html() und print() lassen sich jeweils beliebig oft
aufrufen, in beliebiger Reihenfolge — alles erscheint in der
Reihenfolge, in der es hinzugefügt wurde.
Einen Datensatz auszugeben schlägt nie fehl: print(person) oder
f"{person}" zeigt einen ganzen Datensatz so, wie row() ihn
beschriftet („Root Ancestor [I0001]“), ein Datum als Datum und alles
andere — ein filter()-Ergebnis, person.primary_name — als seine
schlichten Felder.
total_people = db.get_number_of_people()
women = db.get_number_of("person", where="gender == Person.FEMALE")
no_birth_date = db.get_number_of("person", where="birth.date.sortval is None")db.get_number_of_<type>() ist eine günstige Zählung des ganzen
Stammbaums — die Gesamtzahl kommt in einem Antwort-Header zurück, es
werden keine Datensätze tatsächlich heruntergeladen. db. get_number_of(object_type, where=...) ist das bedingte Äquivalent,
für wenn die Zählung selbst einen Filter braucht.
Eine where=-Bedingung kann über eine Beziehung reichen, um darauf zu
filtern — das Geburtsereignis einer Person, der Vater einer Familie,
der Ort eines Ereignisses — auch wenn people()/families()/usw.
immer ein ganzes, unabhängiges Objekt zurückgeben, ohne Möglichkeit, ein
verwandtes Feld direkt zurückzubekommen:
born_and_died_same_place = people(
"birth.place.title == death.place.title and birth.place.title is not None",
order=[{"column": "surname", "direction": "asc"}],
limit=50,
)Ein verwandtes Feld für die Anzeige zurückzubekommen (statt nur
darauf zu filtern) ist ein separates, von Hand durchgeführtes
Nachschlagen über die eigenen Methoden von db —
db.get_event_from_handle(...) und so weiter —, das derselben
Handle-Indirektion folgt, die Gramps' eigenes Datenmodell intern
verwendet.
Sammlungen funktionieren auch: any(c.given_name == 'Steve' for c in children) trifft auf eine Familie zu, wenn irgendein Kind eine
Bedingung erfüllt, und len([c for c in children if c.gender == Person.MALE]) > 1 zählt nur die Söhne. Siehe
GOQL für die vollständige Geschichte zu
Sammlungen, einschließlich ihrer aktuellen Grenze (ein Sprung in eine
Sammlung hinein, kein weiteres Verketten über das darin Enthaltene
hinaus).
record = get_selected()
if record is not None:
if isinstance(record, Person):
...get_selected() gibt den Datensatz zurück, der gerade im eigenen
Detailbereich der Ansicht geöffnet ist — None, wenn nichts ausgewählt
ist, oder wenn es aus der eigenständigen Editor-Vorschau heraus läuft
(die überhaupt keinen Ansichtskontext hat). Ein Netzwerkabruf beim
ersten Aufruf in einem Durchlauf, danach für den Rest davon
zwischengespeichert. isinstance(record, Person) (usw.) sagt, welche
Art von Datensatz man bekommen hat, was für ein an „Alle“-Ansichten
angehängtes Gramplet wichtig ist. Damit sich das live aktualisiert,
während man sich durch die Liste klickt, „Bei Änderung des
ausgewählten Datensatzes automatisch neu ausführen“ im eigenen Editor
des Gramplets einschalten — ausgeschaltet (der Standard) funktioniert
get_selected() weiterhin, spiegelt aber nur wider, was beim letzten
Mal ausgewählt war, als das Gramplet aus irgendeinem anderen Grund
zufällig lief.
Ein Gramplet kann seine eigene Abfrage über den gerade auf die Ansicht angewendeten Filter legen — entweder ins eigene Suchfeld der Ansicht eingetippt oder über die „Filter“-Auswahl angewendet (ein gespeicherter Filter oder eine eigene Regel) —, sodass das Herunterfiltern der Liste auf einen Zweig des Stammbaums die eigene Ausgabe des Gramplets auf dieselbe Weise einschränkt:
rows = filter(
"person",
where=and_filters(get_filter(), "birth.date.sortval is not None and death.date.sortval is not None"),
)Das braucht ein gesetztes views: [...] im Manifest (get_filter()
gibt nur einen where_expr passend zum eigenen Objekttyp der Ansicht
zurück) und listensToFilter: true, damit sich das Gramplet
tatsächlich neu rendert, wenn sich der Filter ändert.
pygal, matplotlib und plotly sind alle vorgebündelt — ein schlichtes
import (oder, für plotly, from plotly.subplots import make_subplots) funktioniert offline, kein %pip install nötig.
print(fig) auf einer plotly-Figure, ein SVG-String von pygal, oder
eine matplotlib-Figure wird automatisch erkannt und als Diagramm
gerendert — kein manuelles to_html()/Einbetten nötig.
Für Diagrammdaten what=[...] übergeben, wenn nur ein oder zwei Felder
gebraucht werden — people() allein lädt jedes Feld jedes Datensatzes
herunter, was für ein Diagramm verschwenderisch ist, das nur, sagen wir,
das Sterbealter plottet.
col1, col2 = st.columns([2, 1])
with col1:
row("Breitere Spalte")
with col2:
row("Schmalere Spalte")st.columns(spec) legt spec Bereiche nebeneinander an — eine
Ganzzahl für so viele gleich breite Spalten, oder eine Liste von
Gewichten (st.columns([2, 1])) für proportionale Breiten — und gibt
einen Bereich pro Spalte zurück. Alles, was innerhalb von with col:
geschrieben wird (row()/html()/print(), oder ein anderer
st.*-Aufruf, einschließlich eines verschachtelten st.columns()),
landet in dieser Spalte statt auf oberster Ebene; col.write(x)
funktioniert genauso, ohne einen with-Block.
Gramplets → Neues Gramplet… öffnet den Gramplet-Editor, bereit für
diese Art (bei einem bestehenden Art auf Gramplet — öffnet sich in
einem Fenster aus dem Gramplets-Menü setzen). Anstelle der
Einstellungen für Ansichts-Gramplets gibt es ein Feld Kategorie, das
es im Gramplets-Menü mit anderen gruppiert (z. B. chart); leer landet
es unter Sonstige.
Der Code läuft, sobald sich das Fenster öffnet. Um zuerst nach einer
Eingabe zu fragen, zeichnen Sie sie mit den st.*-Widgets und erledigen
die eigentliche Arbeit hinter einem st.button. Sie folgen den Namen
und Argumenten von
Streamlit:
st.button, st.text_input, st.number_input, st.checkbox,
st.selectbox, st.radio, st.columns, st.progress, die
Meldungsboxen st.info / st.success / st.warning / st.error und
st.session_state. In Ansichts-Gramplets funktionieren sie genauso.
Für ein lang laufendes Gramplet zeigen Sie mit
st.progress(value, text) einen Balken an, wie in Streamlit: value
ist 0–100 (oder 0.0–1.0), und .progress(...) des zurückgegebenen
Balkens bewegt ihn an Ort und Stelle, .empty() entfernt ihn — günstig
genug, um es einmal pro Zeile aufzurufen. Das funktioniert auch in
Ansichts-Gramplets. people(..., limit=None) (und die anderen
Listenfunktionen) liefern jeden Treffer statt der ersten 50:
surname = st.text_input("Surname", "Smith")
if st.button("Run"):
everyone = people(f"primary_name.surname_list[0].surname == '{surname}'", limit=None)
bar = st.progress(0, text="Checking people...")
for i, person in enumerate(everyone):
bar.progress((i + 1) / len(everyone))
...
bar.empty()%pip install unidecode
from unidecode import unidecode%pip install ist Jupyter-Magic-Syntax, vor der Ausführung des eigenen
Codes in einen echten Aufruf await micropip.install([...])
umgeschrieben — es muss in einer eigenen Zeile stehen, ganz oben, genau
wie in einem Notebook (es zum Beispiel in ein if zu schreiben,
funktioniert nicht). Das funktioniert nur für reine Python-Pakete
ohne kompilierten Code/C-Erweiterungen; pygal/matplotlib/plotly
brauchen es nicht, da sie vorgebündelt sind, aber die meisten kleinen,
reinen Python-Hilfspakete auf PyPI installieren sich auf diese Weise
problemlos.
Ein Gramplet (kein Ansichts-Gramplet — diese ändern nie Daten) kann Datensätze ändern und löschen, sobald im Gramplet-Editor Datenänderungen erlauben eingeschaltet ist. Die API ist dieselbe, die Gram.py Script in Gramps Desktop verwendet: Einen Datensatz ändert man so, wie man jedes Python-Objekt ändern würde.
for person in people("len(citation_list) == 0", limit=None):
person.private = TrueMark Unsourced People Private aus dem Store ist ein vollständiges
Beispiel: Es findet Personen zuerst günstig mit filter(), holt dann
nur diejenigen, die es braucht, und ändert sie, innerhalb einer
benannten begin_changes()-Gruppe. Tag People zeigt das Hinzufügen
zu einer Liste (person.tag_list.append(tag.handle) — Datensätze
speichern Tags als Handles, nicht als Namen) und eine st.selectbox(),
in der Sie eine Auswahl treffen müssen, bevor sich etwas ändert.
-
record.field = valueändert ein Feld, auch verschachtelte (person.primary_name.first_name = "Chris"). Ebenso Listenänderungen (.append(),.pop(), ...) und jedeset_*()-Methode, z. B.person.set_privacy(True). -
delete(record)löscht einen Datensatz. Verweise darauf aus anderen Datensätzen werden dabei nicht entfernt. -
begin_changes("message")/end_changes()fassen die Änderungen dazwischen unter dieser Nachricht zusammen. Optional: Ohne sie ist der ganze Lauf eine Gruppe, benannt nach dem Gramplet.
Datensätze aus people() und den anderen Pluralfunktionen,
get_raw_object(), get_object(), get_selected() und db.* können
geändert werden. Ergebnisse von filter() nicht, da sie nur einige
Felder eines Datensatzes enthalten. Fragt man in einem Lauf zweimal nach
demselben Datensatz, erhält man dasselbe Objekt zurück, mit den eigenen
Änderungen darin.
Zuerst ein Probelauf. Im Gramplet-Editor ist jeder Lauf ein
Probelauf, und auch das Öffnen des Gramplet-Fensters aus dem
Gramplets-Menü macht einen Probelauf. Nichts wird gespeichert; unter der
Ausgabe listet ein gelber Kasten Probelauf jeden Datensatz auf, der
sich ändern würde, mit dem alten und neuen Wert jedes geänderten Felds,
sowie jeden Datensatz, der gelöscht würde, mit der Anzahl der anderen
Datensätze, die noch auf ihn verweisen. Jeder geänderte Datensatz wird
zuerst gegen sein Gramps-Schema geprüft, dieselbe Prüfung, die der
Server macht; ein ungültiger Wert stoppt den Lauf und nennt das Feld.
Ein Punkt, in dem sich ein Probelauf vom echten Lauf unterscheiden kann:
Suchen (filter(), count(), where=) laufen auf dem Server und sehen
daher den Stammbaum noch so, wie er vor den Änderungen des Laufs war.
Dann Ausführen. Das Fenster des Gramplets hat die Schaltflächen
Probelauf und Ausführen (das Menü markiert solche Gramplets mit
✎). Ausführen speichert jede Gruppe von Änderungen, sobald der Code
sie schließt — bei end_changes() oder am Ende des Laufs — als eine
Transaktion, sodass sie als ein Eintrag im Verlauf des Stammbaums
erscheint. Solange das Ergebnis eines echten Laufs angezeigt wird, steht
in der Titelleiste Änderungen werden gespeichert, und der Kasten
unter der Ausgabe wird grün und markiert jede Gruppe als gespeichert.
Lehnt der Server eine Gruppe ab — höchstwahrscheinlich, weil jemand
anderes einen ihrer Datensätze geändert hat, nachdem das Gramplet ihn
gelesen hat —, wird nichts von dieser Gruppe gespeichert, der Lauf
stoppt mit einem Fehler, und zuvor gespeicherte Gruppen bleiben
gespeichert. Widgets in der Ausgabe (st.button("Apply") usw.) führen
den Code in dem Modus erneut aus, aus dem das aktuelle Ergebnis stammt,
sodass ein Gramplet in einem Probelauf zeigen kann, was es ändern würde,
und es nach Ausführen anwenden kann.
Rückgängig. Nach einem echten Lauf zeigt das Fenster, wie viele Gruppen es gespeichert hat, und eine Schaltfläche Rückgängig, die sie zurücknimmt, die letzte zuerst. Rückgängig verweigert eine Gruppe, deren Datensätze inzwischen jemand geändert hat, statt diese Bearbeitungen zu überschreiben. Es wird nur für das aktuelle Ergebnis des Fensters angeboten: Ein erneuter Lauf des Gramplets oder das Schließen seines Fensters beendet es.
Ein Ordner pro Gramplet in
gramplet-store/,
benannt nach der eigenen id (der Ordnername und das eigene
id-Feld des Manifests müssen übereinstimmen):
gramplet-store/
<slug>/
manifest.json # erforderlich
code.py # erforderlich -- der Python-Quellcode des Gramplets
icon.png # optional (png/jpg/jpeg/svg/webp)
Felder von manifest.json:
| Feld | Erforderlich | Bedeutung |
|---|---|---|
id |
ja | Muss mit dem Ordnernamen übereinstimmen. |
name |
ja | Wird als Titel des Eintrags im Store angezeigt. |
description |
ja | Wird unter dem Namen im Store angezeigt. |
version |
ja | Schlichtes Semver, z. B. "1.0.0" — bei jeder Änderung von code.py oder des Manifests erhöhen, damit installierte Kopien erkennen können, dass ein Update verfügbar ist. |
author |
ja | Wird im Store angezeigt. |
category |
ja | Ein kurzes Etikett ("example", "detail", "utility", "chart", …), um Einträge zu gruppieren/filtern. |
views |
nein | Zu welchen Objekttyp-Ansichten ("person", "family", …) dieses Gramplet hinzugefügt werden kann. Weglassen für „jeder Typ“. |
listensToSelection |
nein | Ob dieses Gramplet neu ausgeführt werden soll, wenn sich der ausgewählte Datensatz ändert. |
listensToFilter |
nein | Ob dieses Gramplet neu ausgeführt werden soll, wenn sich der aktive Filter ändert (Suchfeld oder Filter-Auswahl). |
kind |
nein |
"view" (Standard) für ein Ansichts-Gramplet; "window" für ein Gramplet, das aus dem Gramplets-Menü läuft. views/listensTo* gelten nur für Ansichts-Gramplets. |
allowChanges |
nein |
true, um es mit eingeschaltetem Datenänderungen erlauben zu installieren (der Store zeigt ein Abzeichen Ändert Ihre Daten). Nur mit "kind": "window" erlaubt. |
Nach dem Hinzufügen oder Bearbeiten eines Eintrags catalog.json neu
erzeugen — die einzige Datei, die die App tatsächlich abruft:
npm --prefix app run build:gramplet-catalogDas validiert jeden Eintrag (erforderliche Felder vorhanden, id
passt zu seinem Ordner, version sieht wie Semver aus, code.py nicht
leer, kind gültig) und schlägt beim ersten Problem laut fehl, statt einen defekten
Katalog zu veröffentlichen.
Gramplets können Datensätze ändern und löschen (siehe Daten ändern), aber noch keine neuen anlegen. Es gibt auch noch kein Äquivalent für andere Gramps-Add-on-Typen (Werkzeuge, Berichte). Siehe Roadmap und bekannte Einschränkungen für den aktuellen Stand dazu.