First visit ends in a sheet, not in an empty box
A fresh print-only install showed "Pick from the library" as the second way in — on an instance that has no library, because the library belongs to the AI module. The button opened an empty picker. It is gone when the module is off (`hasLibrary`), together with the two smaller "from the library" links. In its place: "Try it with a sample photo". It fetches public/samples/lake-at-dusk.jpg — an original illustration drawn for this, no third-party material — and sends it through the normal upload, so everything after it is what happens with a real picture. Checked in the browser, English and German, on a print-only instance: no library button, sample loads, the print button enables, the PDF measures 210.00 x 297.00 mm, no failed request.
This commit is contained in:
5 files changed
+42
-9
No files matched your search
@@ -0,0 +1,9 @@
|
|||||||
|
# Sample image
|
||||||
|
|
||||||
|
`lake-at-dusk.jpg` is an original illustration drawn for Klarbild as vector
|
||||||
|
shapes and rendered to JPEG. It contains no third-party material and is
|
||||||
|
covered by the repository's licence like everything else here.
|
||||||
|
|
||||||
|
It exists so that the first visit to a fresh instance can end in a printed
|
||||||
|
sheet: the "Try it with a sample photo" button in the print studio uploads
|
||||||
|
it through the normal upload path.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -110,8 +110,8 @@ function shrink(dataUrl: string, maxEdge = 700): Promise<{ url: string; w: numbe
|
|||||||
|
|
||||||
/* ========================================================================== */
|
/* ========================================================================== */
|
||||||
|
|
||||||
export default function PrintApp({ locale, dict, canDeliver = false }:
|
export default function PrintApp({ locale, dict, canDeliver = false, hasLibrary = true }:
|
||||||
{ locale?: Locale; dict?: Dict | null; canDeliver?: boolean }) {
|
{ locale?: Locale; dict?: Dict | null; canDeliver?: boolean; hasLibrary?: boolean }) {
|
||||||
const t = useT(dict);
|
const t = useT(dict);
|
||||||
const [cells, setCells] = useState<Cell[]>([]);
|
const [cells, setCells] = useState<Cell[]>([]);
|
||||||
const [paperId, setPaperId] = useState('A4');
|
const [paperId, setPaperId] = useState('A4');
|
||||||
@@ -335,6 +335,17 @@ export default function PrintApp({ locale, dict, canDeliver = false }:
|
|||||||
} catch { notify(t('These settings could not be applied.'), true); }
|
} catch { notify(t('These settings could not be applied.'), true); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A photo to try it with, for the first visit — before anyone has picked a
|
||||||
|
// picture of their own. It goes through the normal upload, so what follows is
|
||||||
|
// exactly what happens with a real image. (An original illustration made for
|
||||||
|
// Klarbild; public/samples/README.md says so.)
|
||||||
|
const addSample = async () => {
|
||||||
|
try {
|
||||||
|
const blob = await fetch('/samples/lake-at-dusk.jpg').then((r) => { if (!r.ok) throw new Error(); return r.blob(); });
|
||||||
|
await addFiles([new File([blob], 'lake-at-dusk.jpg', { type: 'image/jpeg' })]);
|
||||||
|
} catch { notify(t('The sample photo could not be loaded.')); }
|
||||||
|
};
|
||||||
|
|
||||||
const addFromLibrary = (it: any) => {
|
const addFromLibrary = (it: any) => {
|
||||||
const [w, h] = String(it.output_px || '').split(/[x×]/).map((n: string) => parseInt(n, 10) || 0);
|
const [w, h] = String(it.output_px || '').split(/[x×]/).map((n: string) => parseInt(n, 10) || 0);
|
||||||
const id = uid();
|
const id = uid();
|
||||||
@@ -500,7 +511,7 @@ export default function PrintApp({ locale, dict, canDeliver = false }:
|
|||||||
<div className="col work-left">
|
<div className="col work-left">
|
||||||
<Card nr="1" title={t('Images')} actions={
|
<Card nr="1" title={t('Images')} actions={
|
||||||
<>
|
<>
|
||||||
<button className="link" onClick={() => setPicker(true)}>{t('From the library')}</button>
|
{hasLibrary && <button className="link" onClick={() => setPicker(true)}>{t('From the library')}</button>}
|
||||||
{cells.length > 0 && (
|
{cells.length > 0 && (
|
||||||
<button className="link danger"
|
<button className="link danger"
|
||||||
onClick={() => { if (confirm(t('Remove {images}?',
|
onClick={() => { if (confirm(t('Remove {images}?',
|
||||||
@@ -528,10 +539,20 @@ export default function PrintApp({ locale, dict, canDeliver = false }:
|
|||||||
already in Klarbild and need not be exported and uploaded
|
already in Klarbild and need not be exported and uploaded
|
||||||
again first. */}
|
again first. */}
|
||||||
<div className="or"><span>{t('or')}</span></div>
|
<div className="or"><span>{t('or')}</span></div>
|
||||||
<button type="button" className="drop drop-lib" onClick={() => setPicker(true)}>
|
{hasLibrary ? (
|
||||||
<span className="drop-title">{t('Pick from the library')}</span>
|
<button type="button" className="drop drop-lib" onClick={() => setPicker(true)}>
|
||||||
<span className="drop-hint">{t('Everything Klarbild has already made — with search, by folder, and several at once')}</span>
|
<span className="drop-title">{t('Pick from the library')}</span>
|
||||||
</button>
|
<span className="drop-hint">{t('Everything Klarbild has already made — with search, by folder, and several at once')}</span>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
// Print-only instance: there is no library to pick from. The
|
||||||
|
// second way in is a sample, so the first visit ends in a
|
||||||
|
// sheet and not in an empty box.
|
||||||
|
<button type="button" className="drop drop-lib" onClick={addSample}>
|
||||||
|
<span className="drop-title">{t('Try it with a sample photo')}</span>
|
||||||
|
<span className="drop-hint">{t('No picture at hand? Take ours, pick a preset below and make the PDF.')}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{presets.length > 0 && (
|
{presets.length > 0 && (
|
||||||
<div className="quickstart">
|
<div className="quickstart">
|
||||||
<span className="mono-label">{t('Or start from a preset')}</span>
|
<span className="mono-label">{t('Or start from a preset')}</span>
|
||||||
@@ -559,7 +580,7 @@ export default function PrintApp({ locale, dict, canDeliver = false }:
|
|||||||
))}
|
))}
|
||||||
<div className="cell-add-group">
|
<div className="cell-add-group">
|
||||||
<button className="cell-add" onClick={() => fileRef.current?.click()}>{t('+ Upload')}</button>
|
<button className="cell-add" onClick={() => fileRef.current?.click()}>{t('+ Upload')}</button>
|
||||||
<button className="cell-add" onClick={() => setPicker(true)}>{t('+ From the library')}</button>
|
{hasLibrary && <button className="cell-add" onClick={() => setPicker(true)}>{t('+ From the library')}</button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -564,6 +564,7 @@
|
|||||||
"No mail sending is set up on this server yet — the address is stored, but nothing arrives yet. An admin sets that up under Admin → Email.": "Auf diesem Server ist noch kein E-Mail-Versand eingerichtet — die Adresse wird zwar gespeichert, es kommt aber noch nichts an. Ein Admin richtet das unter Admin → E-Mail ein.",
|
"No mail sending is set up on this server yet — the address is stored, but nothing arrives yet. An admin sets that up under Admin → Email.": "Auf diesem Server ist noch kein E-Mail-Versand eingerichtet — die Adresse wird zwar gespeichert, es kommt aber noch nichts an. Ein Admin richtet das unter Admin → E-Mail ein.",
|
||||||
"No models yet. Add one from the available ones:": "Noch keine Modelle. Aus verfügbaren hinzufügen:",
|
"No models yet. Add one from the available ones:": "Noch keine Modelle. Aus verfügbaren hinzufügen:",
|
||||||
"No paired chats yet.": "Noch keine gekoppelten Chats.",
|
"No paired chats yet.": "Noch keine gekoppelten Chats.",
|
||||||
|
"No picture at hand? Take ours, pick a preset below and make the PDF.": "Gerade kein Bild zur Hand? Nimm unseres, wähl unten eine Vorlage und mach das PDF.",
|
||||||
"No sending set up yet — forgotten password, job notices and mailing share links do not work until then.": "Noch kein Versand eingerichtet — Passwort vergessen, Auftragshinweise und der Mailversand von Teilen-Links funktionieren bis dahin nicht.",
|
"No sending set up yet — forgotten password, job notices and mailing share links do not work until then.": "Noch kein Versand eingerichtet — Passwort vergessen, Auftragshinweise und der Mailversand von Teilen-Links funktionieren bis dahin nicht.",
|
||||||
"No token generated yet.": "Noch kein Token erzeugt.",
|
"No token generated yet.": "Noch kein Token erzeugt.",
|
||||||
"No valid image size chosen yet.": "Noch kein gültiges Bildmaß gewählt.",
|
"No valid image size chosen yet.": "Noch kein gültiges Bildmaß gewählt.",
|
||||||
@@ -849,6 +850,7 @@
|
|||||||
"The password is stored encrypted and never shown again.": "Das Passwort wird verschlüsselt gespeichert und nie wieder angezeigt.",
|
"The password is stored encrypted and never shown again.": "Das Passwort wird verschlüsselt gespeichert und nie wieder angezeigt.",
|
||||||
"The problem": "Das Problem",
|
"The problem": "Das Problem",
|
||||||
"The same image once more — for a second format, say": "Dasselbe Bild noch einmal — z. B. für ein zweites Format",
|
"The same image once more — for a second format, say": "Dasselbe Bild noch einmal — z. B. für ein zweites Format",
|
||||||
|
"The sample photo could not be loaded.": "Das Beispielfoto ließ sich nicht laden.",
|
||||||
"The second way out for large sizes: the image is spread across several sheets. The tiles overlap by a glue flap, which is printed on both neighbouring sheets and marked with a dashed line. Every sheet carries its number with row and column.": "Der zweite Weg für große Maße: Das Bild wird auf mehrere Blätter verteilt. Die Kacheln überlappen sich um einen Klebefalz, der auf beiden Nachbarblättern gedruckt und gestrichelt markiert wird. Jedes Blatt trägt seine Nummer samt Reihe und Spalte.",
|
"The second way out for large sizes: the image is spread across several sheets. The tiles overlap by a glue flap, which is printed on both neighbouring sheets and marked with a dashed line. Every sheet carries its number with row and column.": "Der zweite Weg für große Maße: Das Bild wird auf mehrere Blätter verteilt. Die Kacheln überlappen sich um einen Klebefalz, der auf beiden Nachbarblättern gedruckt und gestrichelt markiert wird. Jedes Blatt trägt seine Nummer samt Reihe und Spalte.",
|
||||||
"The trash is empty": "Der Papierkorb ist leer",
|
"The trash is empty": "Der Papierkorb ist leer",
|
||||||
"The trash is switched off — deleting is final.": "Der Papierkorb ist abgeschaltet — Löschen ist endgültig.",
|
"The trash is switched off — deleting is final.": "Der Papierkorb ist abgeschaltet — Löschen ist endgültig.",
|
||||||
@@ -878,6 +880,7 @@
|
|||||||
"Trash · {n}": "Papierkorb · {n}",
|
"Trash · {n}": "Papierkorb · {n}",
|
||||||
"True to size": "Maßhaltig",
|
"True to size": "Maßhaltig",
|
||||||
"Try again": "Noch einmal versuchen",
|
"Try again": "Noch einmal versuchen",
|
||||||
|
"Try it with a sample photo": "Mit einem Beispielfoto ausprobieren",
|
||||||
"US visa 51 × 51 mm (2 × 2 in)": "Visum USA 51 × 51 mm (2 × 2 in)",
|
"US visa 51 × 51 mm (2 × 2 in)": "Visum USA 51 × 51 mm (2 × 2 in)",
|
||||||
"Under <b>“File names”</b> the existing library can be brought onto the unique naming scheme. <b>“Check library”</b> first shows what would change and how many names are currently used twice; <b>“Rename now”</b> carries it out. The image files themselves are left untouched — they sit in storage under their id, not under their name. A second run changes nothing. <b>“Deliver again”</b> then uploads every already delivered image under its new name; the old files stay on the delivery target and you clear them away there yourself.": "Unter <b>„Dateinamen\"</b> lässt sich die vorhandene Bibliothek auf das eindeutige Namensschema bringen. <b>„Bibliothek prüfen\"</b> zeigt zuerst, was sich ändern würde und wie viele Namen aktuell doppelt vergeben sind; <b>„Jetzt umbenennen\"</b> setzt es um. Die Bilddateien selbst bleiben dabei unangetastet — sie liegen im Speicher unter ihrer Kennung, nicht unter ihrem Namen. Ein zweiter Lauf ändert nichts mehr. <b>„Erneut ausliefern\"</b> lädt danach alle schon ausgelieferten Bilder unter dem neuen Namen erneut hoch; die alten Dateien bleiben am Ziel liegen und räumst du dort selbst weg.",
|
"Under <b>“File names”</b> the existing library can be brought onto the unique naming scheme. <b>“Check library”</b> first shows what would change and how many names are currently used twice; <b>“Rename now”</b> carries it out. The image files themselves are left untouched — they sit in storage under their id, not under their name. A second run changes nothing. <b>“Deliver again”</b> then uploads every already delivered image under its new name; the old files stay on the delivery target and you clear them away there yourself.": "Unter <b>„Dateinamen\"</b> lässt sich die vorhandene Bibliothek auf das eindeutige Namensschema bringen. <b>„Bibliothek prüfen\"</b> zeigt zuerst, was sich ändern würde und wie viele Namen aktuell doppelt vergeben sind; <b>„Jetzt umbenennen\"</b> setzt es um. Die Bilddateien selbst bleiben dabei unangetastet — sie liegen im Speicher unter ihrer Kennung, nicht unter ihrem Namen. Ein zweiter Lauf ändert nichts mehr. <b>„Erneut ausliefern\"</b> lädt danach alle schon ausgelieferten Bilder unter dem neuen Namen erneut hoch; die alten Dateien bleiben am Ziel liegen und räumst du dort selbst weg.",
|
||||||
"Under the hood": "Unter der Haube",
|
"Under the hood": "Unter der Haube",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const dict = dictFor(locale);
|
|||||||
<p class="page-lead">
|
<p class="page-lead">
|
||||||
{t('No AI: bring images to exact sizes and place several of them at 100% size with crop marks on one sheet — from the biometric passport photo through the school photo set to the 30 × 40 poster.')}
|
{t('No AI: bring images to exact sizes and place several of them at 100% size with crop marks on one sheet — from the biometric passport photo through the school photo set to the 30 × 40 poster.')}
|
||||||
</p>
|
</p>
|
||||||
<PrintApp client:load locale={locale} dict={dict} canDeliver={has('delivery') || has('mirror')} />
|
<PrintApp client:load locale={locale} dict={dict} canDeliver={has('delivery') || has('mirror')} hasLibrary={has('ai')} />
|
||||||
<style>
|
<style>
|
||||||
.page-title{font-family:var(--font-display);font-size:1.6rem;margin:6px 0 4px;letter-spacing:-.02em;}
|
.page-title{font-family:var(--font-display);font-size:1.6rem;margin:6px 0 4px;letter-spacing:-.02em;}
|
||||||
.page-lead{color:var(--soft);font-size:13.5px;margin:0 0 16px;max-width:64ch;line-height:1.55;}
|
.page-lead{color:var(--soft);font-size:13.5px;margin:0 0 16px;max-width:64ch;line-height:1.55;}
|
||||||
|
|||||||
Reference in new issue
Block a user