import { LitElement, html, nothing } from 'lit';
|
import { customElement, property } from 'lit/decorators.js';
|
import { event, type EventRef } from '@dh-software/lit-extensions';
|
import type { Software } from 'bibliothek-types';
|
|
/**
|
* Eine kompakte Katalog-Zeile für einen Software-Eintrag. Feuert Aktions-Events an die App.
|
* Die Aktionen liegen in festen Spalten, damit sie über alle Zeilen hinweg untereinander stehen;
|
* fehlt eine Aktion, bleibt ihre Spalte leer.
|
*
|
* Zusatzinhalte (Plug-ins, Sprachpakete, Vorlagen …) stehen eingerückt direkt unter ihrem Tool
|
* und bilden mit ihm eine Gruppe.
|
*/
|
@customElement('bibliothek-zeile')
|
export class BibliothekZeile extends LitElement {
|
@property({ attribute: false }) software!: Software;
|
/** Weitere Einträge, die zu dieser Software gehören. */
|
@property({ attribute: false }) zusatzinhalte: Software[] = [];
|
@property({ type: Boolean }) lokal = false;
|
/** Zeile eines Zusatzinhalts (eingerückt unter seinem Tool). */
|
@property({ type: Boolean }) eingerueckt = false;
|
/** Ist dieser Eintrag für den Sammel-Download angehakt? */
|
@property({ type: Boolean }) gewaehlt = false;
|
/** Ids aller angehakten Einträge — nötig, damit auch eingerückte Zeilen ihren Zustand kennen. */
|
@property({ attribute: false }) auswahl: ReadonlySet<number> = new Set();
|
|
@event({ bubbles: true, composed: true }, 'auswahlGeaendert')
|
private auswahlGeaendert!: EventRef<{ software: Software; gewaehlt: boolean }>;
|
|
@event({ bubbles: true, composed: true }, 'herunterladen')
|
private herunterladen!: EventRef<Software>;
|
|
@event({ bubbles: true, composed: true }, 'pruefen')
|
private pruefen!: EventRef<Software>;
|
|
@event({ bubbles: true, composed: true }, 'bearbeiten')
|
private bearbeiten!: EventRef<Software>;
|
|
@event({ bubbles: true, composed: true }, 'loeschen')
|
private loeschen!: EventRef<Software>;
|
|
// Light DOM, damit das globale dh-components-Theme greift.
|
createRenderRoot() {
|
return this;
|
}
|
|
render() {
|
const software = this.software;
|
return html`
|
<div class="zeile ${this.eingerueckt ? 'zeile-eingerueckt' : ''}">
|
<span class="zeile-auswahl">
|
<dh-checkbox
|
?checked=${this.gewaehlt}
|
?disabled=${!software.dateiname}
|
title=${software.dateiname ? 'Für Sammel-Download auswählen' : 'Keine Datei vorhanden'}
|
@isChange=${this.beiAuswahl}
|
></dh-checkbox>
|
</span>
|
<div class="zeile-info">
|
<span class="zeile-name">
|
${software.name}
|
${software.einstufung === 'lizenzpflichtig'
|
? html`<span class="lizenz-marke" title="Lizenzschlüssel über den Support beziehen">lizenzpflichtig</span>`
|
: nothing}
|
${software.einstufung === 'intern' ? html`<span class="intern-marke">intern</span>` : nothing}
|
</span>
|
<span class="zeile-meta">
|
${this.rendereStand()}
|
<span class="${this.statusKlasse()}">${this.statusText()}</span>
|
${this.rendereSignatur()}
|
</span>
|
${this.rendereHinweise()}
|
</div>
|
<div class="zeile-aktionen">
|
<span class="aktion">
|
${software.anleitungUrl
|
? html`<a class="anleitung" href=${software.anleitungUrl} target="_blank" rel="noopener noreferrer">Anleitung</a>`
|
: nothing}
|
</span>
|
<span class="aktion">
|
${software.dateiname
|
? html`<dh-button
|
icon-button
|
variant="filled"
|
title=${`${software.name} herunterladen`}
|
@click=${this.beiHerunterladen}
|
>
|
<span slot="img" class="fv-icon-download"></span>
|
</dh-button>`
|
: nothing}
|
</span>
|
${this.lokal
|
? html`
|
<span class="aktion">
|
${software.quelleTyp && software.quelleTyp !== 'manuell'
|
? html`<dh-button buttonText="Prüfen" variant="standard" @click=${this.beiPruefen}></dh-button>`
|
: nothing}
|
</span>
|
<span class="aktion">
|
<dh-button buttonText="Bearbeiten" variant="standard" @click=${this.beiBearbeiten}></dh-button>
|
</span>
|
<span class="aktion">
|
<dh-button buttonText="Löschen" variant="standard" @click=${this.beiLoeschen}></dh-button>
|
</span>
|
`
|
: nothing}
|
</div>
|
</div>
|
${this.zusatzinhalte.map(
|
(zusatzinhalt) => html`<bibliothek-zeile
|
.software=${zusatzinhalt}
|
.auswahl=${this.auswahl}
|
?gewaehlt=${this.auswahl.has(zusatzinhalt.id)}
|
?lokal=${this.lokal}
|
eingerueckt
|
></bibliothek-zeile>`
|
)}
|
`;
|
}
|
|
private beiAuswahl(ereignis: CustomEvent<boolean>): void {
|
this.auswahlGeaendert.dispatch({ software: this.software, gewaehlt: ereignis.detail });
|
}
|
|
/**
|
* Hinweise zum Umgang mit der Datei: was aus der Programmart folgt, plus der
|
* freie Hinweis am Eintrag. Die eigene Zeile kostet nur dort Höhe, wo wirklich
|
* etwas zu sagen ist — die meisten Einträge bleiben einzeilig.
|
*/
|
private rendereHinweise() {
|
const software = this.software;
|
const teile: string[] = [];
|
if (software.dateiname && software.programmart === 'installer') {
|
teile.push('Datei kann nach der Installation gelöscht werden');
|
}
|
if (software.dateiname && software.programmart === 'eigenstaendig') {
|
teile.push('Läuft direkt — an einen festen Ort legen');
|
}
|
if (software.hinweis) {
|
teile.push(software.hinweis);
|
}
|
return teile.length > 0 ? html`<span class="zeile-hinweis">${teile.join(' · ')}</span>` : nothing;
|
}
|
|
/**
|
* Signaturbefund der bereitgestellten Datei — nur in der Verwaltung.
|
* Für die Netzwerk-Ansicht ist das Betriebswissen ohne Nutzen: Wer etwas
|
* herunterlädt, kann daraus keine Entscheidung ableiten.
|
*/
|
private rendereSignatur() {
|
const software = this.software;
|
if (!this.lokal || !software.dateiname || !software.signaturStatus) {
|
return nothing;
|
}
|
if (software.signaturStatus === 'Valid') {
|
return html` ·
|
<span class="signatur-gut" title="Gültig signiert von ${software.signaturHerausgeber}">
|
signiert: ${software.signaturHerausgeber}
|
</span>`;
|
}
|
const text = software.signaturStatus === 'NotSigned' ? 'unsigniert' : `Signatur unklar (${software.signaturStatus})`;
|
return html` · <span class="signatur-unklar" title="Windows-Signaturstatus: ${software.signaturStatus}">${text}</span>`;
|
}
|
|
/**
|
* Zeigt bevorzugt das Veröffentlichungsdatum des Anbieters — das ist die Angabe,
|
* die den Nutzer interessiert. Nur wenn die Quelle keins liefert, wird ersatzweise
|
* unser Abrufzeitpunkt genannt (und als solcher benannt).
|
*/
|
private rendereStand() {
|
const software = this.software;
|
if (software.veroeffentlichtAm) {
|
return html`Stand ${this.alsDatum(software.veroeffentlichtAm)} · `;
|
}
|
if (software.dateiStandVom) {
|
return html`geholt ${this.alsDatum(software.dateiStandVom)} · `;
|
}
|
return nothing;
|
}
|
|
private alsDatum(zeitstempel: string): string {
|
const zeitpunkt = new Date(zeitstempel);
|
return Number.isNaN(zeitpunkt.getTime()) ? '—' : zeitpunkt.toLocaleDateString('de-DE');
|
}
|
|
private statusText(): string {
|
const software = this.software;
|
if (software.dateiname) {
|
return software.dateiname;
|
}
|
if (!software.quelleTyp) {
|
return 'keine Quelle';
|
}
|
if (software.quelleTyp === 'manuell') {
|
return 'keine Datei hinterlegt';
|
}
|
if (software.letzterAbgleich && !software.abgleichErfolg) {
|
return software.abgleichMeldung || 'Abgleich fehlgeschlagen';
|
}
|
return 'Quelle hinterlegt, noch nicht geladen';
|
}
|
|
private statusKlasse(): string {
|
const software = this.software;
|
if (software.dateiname) {
|
return 'status-ok';
|
}
|
if (!software.quelleTyp || software.quelleTyp === 'manuell' || (software.letzterAbgleich && !software.abgleichErfolg)) {
|
return 'status-fehler';
|
}
|
return 'status-ausstehend';
|
}
|
|
private beiHerunterladen(): void {
|
this.herunterladen.dispatch(this.software);
|
}
|
|
private beiPruefen(): void {
|
this.pruefen.dispatch(this.software);
|
}
|
|
private beiBearbeiten(): void {
|
this.bearbeiten.dispatch(this.software);
|
}
|
|
private beiLoeschen(): void {
|
this.loeschen.dispatch(this.software);
|
}
|
}
|
|
declare global {
|
interface HTMLElementTagNameMap {
|
'bibliothek-zeile': BibliothekZeile;
|
}
|
}
|