Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20c7fd8813 |
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm"
|
name = "tesm"
|
||||||
version = "2.3.2"
|
version = "2.3.3"
|
||||||
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -238,6 +238,16 @@
|
|||||||
<span class="field__label">Dateiname (ohne Endung)</span>
|
<span class="field__label">Dateiname (ohne Endung)</span>
|
||||||
<input class="input" data-viewer-name autocomplete="off">
|
<input class="input" data-viewer-name autocomplete="off">
|
||||||
</label>
|
</label>
|
||||||
|
<label class="viewer__feld">
|
||||||
|
<span class="field__label">Speichern als</span>
|
||||||
|
{# Zwei Wege, beide gewollt: weiter ausfüllbar (das Bild wird das
|
||||||
|
Aussehen des Knopfes und lässt sich später austauschen) oder
|
||||||
|
abgeschlossen (alle Felder werden Teil der Seite). #}
|
||||||
|
<select class="select" data-viewer-modus>
|
||||||
|
<option value="formular">Formular – weiter ausfüllbar</option>
|
||||||
|
<option value="fertig">Abgeschlossen – Felder gesperrt</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="check">
|
<label class="check">
|
||||||
<input type="checkbox" data-viewer-ersetzen>
|
<input type="checkbox" data-viewer-ersetzen>
|
||||||
<span>Vorhandene ersetzen</span>
|
<span>Vorhandene ersetzen</span>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm-core"
|
name = "tesm-core"
|
||||||
version = "2.3.2"
|
version = "2.3.3"
|
||||||
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
|
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ def create_base_app(
|
|||||||
#: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide
|
#: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide
|
||||||
#: Anwendungen.
|
#: Anwendungen.
|
||||||
auth_tagline: str = "",
|
auth_tagline: str = "",
|
||||||
app_version: str = "2.3.2",
|
app_version: str = "2.3.3",
|
||||||
built_in_trusted_keys: tuple[str, ...] = (),
|
built_in_trusted_keys: tuple[str, ...] = (),
|
||||||
enable_license: bool = True,
|
enable_license: bool = True,
|
||||||
template_folder: str | None = None,
|
template_folder: str | None = None,
|
||||||
|
|||||||
@@ -215,44 +215,59 @@ async function pdflibLaden(adresse) {
|
|||||||
return pdflibGeladen;
|
return pdflibGeladen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stempelt die gewaehlten Fotos in die Bildfelder.
|
/** Setzt die Fotos ein und schliesst das Formular auf Wunsch ab.
|
||||||
*
|
*
|
||||||
* Eingabe sind die Bytes, die pdf.js nach dem Ausfuellen geschrieben hat --
|
* Eingabe sind die Bytes, die pdf.js nach dem Ausfuellen geschrieben hat --
|
||||||
* die Formularwerte bleiben also erhalten.
|
* die Feldwerte bleiben also erhalten.
|
||||||
|
*
|
||||||
|
* Das Foto wird das **Aussehen des Bildknopfes** und nicht in die Seite
|
||||||
|
* gestempelt: so bleibt der Knopf erhalten, das Bild ist trotzdem zu sehen,
|
||||||
|
* und beim naechsten Oeffnen laesst es sich austauschen. Genau das tut
|
||||||
|
* Acrobat mit "buttonImportIcon".
|
||||||
|
*
|
||||||
|
* ``abschliessen`` rechnet danach alle Felder flach in die Seite: heraus kommt
|
||||||
|
* ein PDF ohne Formular -- nichts mehr zu verstellen.
|
||||||
*/
|
*/
|
||||||
async function fotosEinsetzen(bytes, bilder, adresse) {
|
async function nachbearbeiten(bytes, bilder, adresse, { abschliessen = false } = {}) {
|
||||||
const PDFLib = await pdflibLaden(adresse);
|
const PDFLib = await pdflibLaden(adresse);
|
||||||
const dokument = await PDFLib.PDFDocument.load(bytes);
|
const dokument = await PDFLib.PDFDocument.load(bytes);
|
||||||
const formular = dokument.getForm();
|
const formular = dokument.getForm();
|
||||||
|
|
||||||
for (const eintrag of bilder) {
|
for (const eintrag of bilder) {
|
||||||
const seite = dokument.getPage(eintrag.seite - 1);
|
|
||||||
const jpeg = await dokument.embedJpg(eintrag.foto.bytes);
|
const jpeg = await dokument.embedJpg(eintrag.foto.bytes);
|
||||||
|
const knopf = eintrag.feldname ? formular.getFieldMaybe(eintrag.feldname) : null;
|
||||||
|
if (knopf && typeof knopf.setImage === "function") {
|
||||||
|
knopf.setImage(jpeg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Kein Knopf gefunden: dann eben in die Seite. Damit nichts darueber
|
||||||
|
// liegt, muss das Feld weichen.
|
||||||
|
const seite = dokument.getPage(eintrag.seite - 1);
|
||||||
const [x1, y1, x2, y2] = eintrag.rechteck;
|
const [x1, y1, x2, y2] = eintrag.rechteck;
|
||||||
const kastenBreite = Math.abs(x2 - x1);
|
const kastenBreite = Math.abs(x2 - x1);
|
||||||
const kastenHoehe = Math.abs(y2 - y1);
|
const kastenHoehe = Math.abs(y2 - y1);
|
||||||
// Einpassen statt verzerren: das Seitenverhaeltnis des Fotos bleibt.
|
// Einpassen statt verzerren: das Seitenverhaeltnis des Fotos bleibt.
|
||||||
const faktor = Math.min(kastenBreite / jpeg.width, kastenHoehe / jpeg.height);
|
const faktor = Math.min(kastenBreite / jpeg.width, kastenHoehe / jpeg.height);
|
||||||
const breite = jpeg.width * faktor;
|
|
||||||
const hoehe = jpeg.height * faktor;
|
|
||||||
seite.drawImage(jpeg, {
|
seite.drawImage(jpeg, {
|
||||||
x: Math.min(x1, x2) + (kastenBreite - breite) / 2,
|
x: Math.min(x1, x2) + (kastenBreite - jpeg.width * faktor) / 2,
|
||||||
y: Math.min(y1, y2) + (kastenHoehe - hoehe) / 2,
|
y: Math.min(y1, y2) + (kastenHoehe - jpeg.height * faktor) / 2,
|
||||||
width: breite,
|
width: jpeg.width * faktor,
|
||||||
height: hoehe,
|
height: jpeg.height * faktor,
|
||||||
});
|
});
|
||||||
// Den Knopf aus der fertigen Kopie nehmen: seine Flaeche liegt ueber dem
|
if (knopf) {
|
||||||
// Seiteninhalt und wuerde das Foto verdecken. Zu tun haette er dort auch
|
|
||||||
// nichts mehr -- seine Aktion fuehrt kein Browser aus, und das Bild steht
|
|
||||||
// jetzt in der Seite. Die Vorlage bleibt unberuehrt.
|
|
||||||
try {
|
try {
|
||||||
const feld = formular.getFieldMaybe(eintrag.feldname);
|
formular.removeField(knopf);
|
||||||
if (feld) formular.removeField(feld);
|
|
||||||
} catch (fehler) {
|
} catch (fehler) {
|
||||||
// Bleibt der Knopf liegen, ist das Foto verdeckt -- aber die Datei ist
|
|
||||||
// heil. Das ist der bessere von zwei schlechten Ausgaengen.
|
|
||||||
console.warn("Bildknopf liess sich nicht entfernen", fehler);
|
console.warn("Bildknopf liess sich nicht entfernen", fehler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abschliessen) {
|
||||||
|
// Alle Felder werden Teil der Seite. Danach gibt es kein Formular mehr --
|
||||||
|
// gewollt: so kann niemand mehr etwas verstellen.
|
||||||
|
formular.flatten();
|
||||||
|
}
|
||||||
return dokument.save();
|
return dokument.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,10 +454,11 @@ async function pdfBetrachter(behaelter, seiten, url, basis) {
|
|||||||
* pdf.js schreibt die Feldwerte; gewaehlte Fotos kommen danach mit
|
* pdf.js schreibt die Feldwerte; gewaehlte Fotos kommen danach mit
|
||||||
* pdf-lib hinein -- pdf.js kann nichts in ein PDF zeichnen.
|
* pdf-lib hinein -- pdf.js kann nichts in ein PDF zeichnen.
|
||||||
*/
|
*/
|
||||||
speichern: async (pdflibAdresse) => {
|
speichern: async (pdflibAdresse, { abschliessen = false } = {}) => {
|
||||||
const bytes = await dokument.saveDocument();
|
const bytes = await dokument.saveDocument();
|
||||||
if (!bilder.size) return bytes;
|
// Ohne Bild und ohne Abschluss braucht es pdf-lib gar nicht.
|
||||||
return fotosEinsetzen(bytes, [...bilder.values()], pdflibAdresse);
|
if (!bilder.size && !abschliessen) return bytes;
|
||||||
|
return nachbearbeiten(bytes, [...bilder.values()], pdflibAdresse, { abschliessen });
|
||||||
},
|
},
|
||||||
/** Wurde ueberhaupt etwas eingetragen? */
|
/** Wurde ueberhaupt etwas eingetragen? */
|
||||||
veraendert: () => {
|
veraendert: () => {
|
||||||
@@ -482,6 +498,7 @@ export function initFilePreview(wurzel = document) {
|
|||||||
const ersetzenFeld = stueck("ersetzen");
|
const ersetzenFeld = stueck("ersetzen");
|
||||||
const jetzt = stueck("jetzt");
|
const jetzt = stueck("jetzt");
|
||||||
const abbrechen = stueck("abbrechen");
|
const abbrechen = stueck("abbrechen");
|
||||||
|
const modusFeld = stueck("modus");
|
||||||
const basis = rumpf.dataset.viewerPdfjs || "";
|
const basis = rumpf.dataset.viewerPdfjs || "";
|
||||||
|
|
||||||
// Ein Format, das dieser Browser nicht kann (HEIC ausserhalb von Safari),
|
// Ein Format, das dieser Browser nicht kann (HEIC ausserhalb von Safari),
|
||||||
@@ -619,7 +636,9 @@ export function initFilePreview(wurzel = document) {
|
|||||||
const vorher = jetzt.textContent;
|
const vorher = jetzt.textContent;
|
||||||
jetzt.textContent = "Speichern ...";
|
jetzt.textContent = "Speichern ...";
|
||||||
try {
|
try {
|
||||||
const bytes = await pdf.speichern(rumpf.dataset.viewerPdflib || "");
|
const bytes = await pdf.speichern(rumpf.dataset.viewerPdflib || "", {
|
||||||
|
abschliessen: modusFeld?.value === "fertig",
|
||||||
|
});
|
||||||
const daten = new FormData();
|
const daten = new FormData();
|
||||||
daten.append("pfad", aktuell.pfad);
|
daten.append("pfad", aktuell.pfad);
|
||||||
daten.append("ordner", ordnerFeld.value.trim());
|
daten.append("ordner", ordnerFeld.value.trim());
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm-licensing"
|
name = "tesm-licensing"
|
||||||
version = "2.3.2"
|
version = "2.3.3"
|
||||||
description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver"
|
description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = ["cryptography>=42"]
|
dependencies = ["cryptography>=42"]
|
||||||
|
|||||||
Reference in new issue
Block a user