From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally
---
desktop/frontend/src/components/client-zeile.ts | 135 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 135 insertions(+), 0 deletions(-)
diff --git a/desktop/frontend/src/components/client-zeile.ts b/desktop/frontend/src/components/client-zeile.ts
new file mode 100644
index 0000000..29656d3
--- /dev/null
+++ b/desktop/frontend/src/components/client-zeile.ts
@@ -0,0 +1,135 @@
+import { LitElement, html, nothing } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { event, type EventRef } from '@dh-software/lit-extensions';
+import type { EintragMitBefund, LokalerZustand } from '../bruecke';
+
+/** Was der jeweilige Zustand für den Nutzer bedeutet. */
+const ZUSTAND_TEXT: Record<LokalerZustand, string> = {
+ fehlt: 'nicht auf diesem Rechner',
+ aktuell: 'aktuell',
+ veraltet: 'veraltet',
+ fremd: 'von Hand verändert',
+};
+
+const ZUSTAND_KLASSE: Record<LokalerZustand, string> = {
+ fehlt: 'zustand-fehlt',
+ aktuell: 'zustand-aktuell',
+ veraltet: 'zustand-veraltet',
+ fremd: 'zustand-fremd',
+};
+
+/**
+ * Eine Zeile des Katalogs im Client. Anders als in der Weboberfläche steht hier,
+ * was auf diesem Rechner liegt — das ist der ganze Unterschied.
+ */
+@customElement('client-zeile')
+export class ClientZeile extends LitElement {
+ @property({ attribute: false }) eintrag!: EintragMitBefund;
+ @property({ type: Boolean }) laeuft = false;
+
+ @event({ bubbles: true, composed: true }, 'holen')
+ private holen!: EventRef<EintragMitBefund>;
+
+ @event({ bubbles: true, composed: true }, 'verknuepfen')
+ private verknuepfen!: EventRef<EintragMitBefund>;
+
+ // Light DOM, damit das globale dh-components-Theme greift.
+ createRenderRoot() {
+ return this;
+ }
+
+ render() {
+ const eintrag = this.eintrag;
+ return html`
+ <div class="zeile">
+ <div class="zeile-info">
+ <span class="zeile-name">
+ ${eintrag.name}
+ ${eintrag.einstufung === 'lizenzpflichtig'
+ ? html`<span class="marke lizenz-marke">lizenzpflichtig</span>`
+ : nothing}
+ ${eintrag.einstufung === 'intern' ? html`<span class="marke intern-marke">intern</span>` : nothing}
+ </span>
+ <span class="zeile-meta">${this.rendereStand()}${this.rendereZustand()}</span>
+ ${eintrag.hinweis ? html`<span class="zeile-hinweis">${eintrag.hinweis}</span>` : nothing}
+ </div>
+ <div class="zeile-aktionen">${this.rendereAktion()}${this.rendereVerknuepfung()}</div>
+ </div>
+ `;
+ }
+
+ private rendereStand() {
+ const zeitpunkt = this.eintrag.veroeffentlichtAm || this.eintrag.dateiStandVom;
+ if (!zeitpunkt) {
+ return nothing;
+ }
+ const datum = new Date(zeitpunkt);
+ if (Number.isNaN(datum.getTime())) {
+ return nothing;
+ }
+ return html`Stand ${datum.toLocaleDateString('de-DE')}`;
+ }
+
+ /** Nur bei eigenständigen Programmen aussagekräftig — Installer verraten hier nichts. */
+ private rendereZustand() {
+ const befund = this.eintrag.lokal;
+ if (!befund) {
+ return nothing;
+ }
+ return html` ·
+ <span class=${ZUSTAND_KLASSE[befund.zustand]}>${ZUSTAND_TEXT[befund.zustand]}</span>`;
+ }
+
+ /**
+ * Ein rundes Symbol wie in der Weboberfläche. Was der Klick bewirkt, steht
+ * ohnehin schon als Zustand in der Zeile — die Beschriftung wäre eine
+ * Wiederholung. Steht ein Update an, ist der Knopf gefüllt statt flach.
+ */
+ private rendereAktion() {
+ const eintrag = this.eintrag;
+ if (!eintrag.dateiname) {
+ return html`<span class="zeile-hinweis">keine Datei hinterlegt</span>`;
+ }
+ const zustand = eintrag.lokal?.zustand;
+ const stehtAn = zustand === 'veraltet' || zustand === 'fremd';
+ const zweck = stehtAn ? 'aktualisieren' : zustand === 'aktuell' ? 'erneut holen' : 'holen';
+ return html`<dh-button
+ icon-button
+ variant=${stehtAn ? 'filled' : 'standard'}
+ title=${`${eintrag.name} ${zweck}`}
+ ?disabled=${this.laeuft}
+ @click=${this.beiHolen}
+ >
+ <span slot="img" class="fv-icon-download"></span>
+ </dh-button>`;
+ }
+
+ private rendereVerknuepfung() {
+ const befund = this.eintrag.lokal;
+ if (!befund || befund.zustand === 'fehlt') {
+ return nothing;
+ }
+ return html`<dh-button
+ icon-button
+ variant="standard"
+ title="Verknüpfung auf dem Desktop anlegen"
+ @click=${this.beiVerknuepfen}
+ >
+ <span slot="img" class="fv-icon-link"></span>
+ </dh-button>`;
+ }
+
+ private beiHolen(): void {
+ this.holen.dispatch(this.eintrag);
+ }
+
+ private beiVerknuepfen(): void {
+ this.verknuepfen.dispatch(this.eintrag);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'client-zeile': ClientZeile;
+ }
+}
--
Gitblit v1.9.3