Element-Feld rechts: Text, Masse, Ausrichten, Ebene
Die Auswahl eines Elements war nur eine Zeile im linken Bedienfeld, und die
Bedienung dazu lag zwischen Slide-Einstellungen verstreut. Jetzt ist die
Oberflaeche geteilt: links die Slide und alles Allgemeine, rechts genau das
angeklickte Element.
Im Element-Feld stehen Text, Groesse, X und Y, die Ebene, das Ausrichten an
der Slide (links, mittig, rechts sowie oben, mittig, unten mit 92 px Rand),
das Angleichen ueber alle Slides und das Loeschen. Der Text schreibt in
dieselbe Ueberschreibung, die auch das Inhalt-Feld links fuellt -- man
beschriftet also die Ueberschrift, indem man sie anklickt.
Ausgerichtet wird gemessen statt gerechnet: die Mockups stehen
perspektivisch im Raum und das Blatt ist gepolstert.
Zwei Fehler, die dabei aufgefallen sind:
Das Feld faehrt beim Anklicken ein und macht die Buehne schmaler -- damit
aendert sich der Massstab mitten im Klick. Der Zug rechnete aber mit einem
frisch gerechneten autoFit und verschob das Element deshalb um ein Drittel
zu weit. Genommen wird jetzt der Massstab, mit dem tatsaechlich gezeichnet
ist.
Und ein fehlendes Bild riss den ganzen Export mit ("1 Bild(er) nicht
geladen"), ohne zu sagen welches. Verweise auf Dateien, die es nicht mehr
gibt, werden beim Start aufgeloest und benannt; eine Slide baut sich auch
ohne sie auf.
Geprueft: 13 Faelle fuer das Element-Feld, dazu der volle Exportlauf und die
Maus-Bedienung.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
778aa7bab7
commit
92529a8c47
3 files changed
+275
-83
No files matched your search
@@ -106,15 +106,22 @@ aus dem Browser einmalig hineingeschrieben.
|
||||
| `C` `G` `T` | Durchgehend · Tag-Ausrückung · Perspektive |
|
||||
| `D` `A` | Slide laden · alle als ZIP |
|
||||
|
||||
Im Bearbeiten-Modus lassen sich Elemente ziehen, mit den Pfeiltasten
|
||||
pixelweise schieben (`⇧` = 10 px) und über `A−`/`A+` in der Größe ändern.
|
||||
Dazu kommen im Bedienfeld:
|
||||
Im Bearbeiten-Modus lassen sich Elemente ziehen und mit den Pfeiltasten
|
||||
pixelweise schieben (`⇧` = 10 px).
|
||||
|
||||
| Feld | Wirkung |
|
||||
**Ein angeklicktes Element öffnet sein eigenes Feld rechts.** Damit ist die
|
||||
Oberfläche geteilt: links die Slide und alles Allgemeine, rechts das eine
|
||||
Element.
|
||||
|
||||
| Im Element-Feld | Wirkung |
|
||||
|---|---|
|
||||
| `Text` | Beschriftung des Elements, `*Wort*` setzt den Akzent |
|
||||
| `Schrift` / `Breite`, `A−` `A+` | Größe |
|
||||
| `X` `Y` | Stelle auf der Slide, gezählt ab der Mitte, bezogen auf die obere linke Ecke |
|
||||
| `Ebene` | wie `z-index` — höher liegt weiter vorn. Leer heißt Standard |
|
||||
| `Element löschen` | nimmt es aus der Slide. Es taucht darunter als „… zurück" wieder auf |
|
||||
| `Links` `Mitte` `Rechts`, `Oben` `Mitte` `Unten` | an der Slide ausrichten, 92 px Rand |
|
||||
| `Position` / `Größe angleichen` | denselben Wert auf allen Slides |
|
||||
| `Löschen` | nimmt es aus der Slide. Es taucht darunter als „… zurück" wieder auf |
|
||||
|
||||
**Der Streifen ist eine durchgehende Fläche.** Jede Slide trägt das gesamte
|
||||
Blatt, ein Element lässt sich also über den ganzen Post schieben und nicht nur
|
||||
|
||||
+229
-76
@@ -49,6 +49,35 @@
|
||||
}
|
||||
#slide-ops button { flex: none; padding: 8px 10px; }
|
||||
|
||||
/* Das Feld des gewaehlten Elements. Es steht rechts, damit links die Slide
|
||||
und das Allgemeine bleiben und beides nicht durcheinandergeht. Es faehrt
|
||||
nur bei einer Auswahl ein; die Buehne rechnet ihre Einpassung danach neu,
|
||||
sonst haenge der Streifen schief. */
|
||||
.panel-rechts {
|
||||
display: none;
|
||||
border-right: none; border-left: 1px solid #24262F;
|
||||
padding: 22px 22px 40px 22px;
|
||||
}
|
||||
body[data-elem="an"] .panel-rechts { display: block; }
|
||||
.el-kopf { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
|
||||
.el-kopf h1 { flex: 1; margin: 0; }
|
||||
.el-zu {
|
||||
flex: none; width: 26px; height: 26px; border-radius: 7px; cursor: pointer;
|
||||
background: #22242C; color: #8A8F9B; border: 1px solid #2E313B;
|
||||
font-family: inherit; font-size: 13px; line-height: 1;
|
||||
}
|
||||
.el-zu:hover { background: #2A2D36; color: #C7CCD6; }
|
||||
#el-inhalt textarea {
|
||||
width: 100%; box-sizing: border-box; resize: vertical; min-height: 62px;
|
||||
background: #22242C; color: #D6DAE3; border: 1px solid #2E313B;
|
||||
border-radius: 7px; padding: 7px 8px;
|
||||
font-family: inherit; font-size: 12px; line-height: 1.45;
|
||||
}
|
||||
#el-inhalt textarea:focus { outline: none; border-color: var(--accent-yellow); }
|
||||
#el-inhalt .abschnitt { font-size: 11px; color: #8A8F9B; letter-spacing: .09em;
|
||||
text-transform: uppercase; margin: 14px 0 6px; }
|
||||
#el-inhalt .opts button { flex: 1; }
|
||||
|
||||
/* Zielmarke beim Ablegen einer Datei. */
|
||||
.holder.ablegen::before {
|
||||
content: ""; position: absolute; inset: 0; z-index: 5; pointer-events: none;
|
||||
@@ -137,29 +166,26 @@
|
||||
border-radius: 4px; padding: 1px 6px; font-family: ui-monospace, monospace;
|
||||
font-size: 11px; color: #C7CCD6;
|
||||
}
|
||||
.edit-info {
|
||||
font-size: 12px; line-height: 1.55; color: #C7CCD6;
|
||||
background: #1D1F26; border: 1px solid #2E313B; border-radius: 8px;
|
||||
padding: 9px 11px; margin-bottom: 9px; min-height: 56px;
|
||||
}
|
||||
.edit-info .dim { color: #6E7480; }
|
||||
#el-inhalt { font-size: 12px; line-height: 1.55; color: #C7CCD6; }
|
||||
#el-inhalt .dim { color: #6E7480; }
|
||||
/* Ziehen ist gut fuer grob, tippen fuer genau. Ohne Eingabefelder laesst
|
||||
sich ein Wert nicht ueber mehrere Slides hinweg exakt gleich setzen. */
|
||||
.edit-info .fields {
|
||||
#el-inhalt .fields {
|
||||
display: grid; grid-template-columns: 42px 1fr 20px 1fr;
|
||||
gap: 6px 7px; align-items: center; margin-top: 8px;
|
||||
gap: 6px 7px; align-items: center;
|
||||
}
|
||||
.edit-info label { color: #6E7480; font-size: 11px; }
|
||||
.edit-info input {
|
||||
#el-inhalt label { color: #6E7480; font-size: 11px; }
|
||||
#el-inhalt input {
|
||||
width: 100%; box-sizing: border-box;
|
||||
background: #14161C; border: 1px solid #343845; border-radius: 6px;
|
||||
color: #E7EAF0; font: inherit; font-variant-numeric: tabular-nums;
|
||||
padding: 4px 6px; text-align: right; -moz-appearance: textfield;
|
||||
}
|
||||
.edit-info input::-webkit-outer-spin-button,
|
||||
.edit-info input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.edit-info input:focus { outline: none; border-color: var(--accent-yellow); }
|
||||
.edit-info .hint { color: #6E7480; margin-top: 6px; }
|
||||
#el-inhalt input::-webkit-outer-spin-button,
|
||||
#el-inhalt input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
#el-inhalt input:focus { outline: none; border-color: var(--accent-yellow); }
|
||||
#el-inhalt .hint { color: #6E7480; margin-top: 6px; }
|
||||
#el-inhalt .row { margin-top: 0; }
|
||||
.std { margin-bottom: 22px; }
|
||||
.std summary {
|
||||
cursor: pointer; font-size: 11px; font-weight: 700; letter-spacing: .09em;
|
||||
@@ -843,24 +869,12 @@
|
||||
|
||||
<details class="group" data-grp="bearbeiten">
|
||||
<summary>Bearbeiten</summary>
|
||||
<div class="opts" id="editmodes" style="margin-bottom:8px">
|
||||
<div class="opts" id="editmodes">
|
||||
<button data-e="an" aria-pressed="false">An</button>
|
||||
<button data-e="aus" aria-pressed="true">Aus</button>
|
||||
</div>
|
||||
<div class="edit-info" id="edit-info"><span class="dim">Nichts ausgewählt</span></div>
|
||||
<div class="row" style="margin-bottom:8px">
|
||||
<button class="btn secondary" id="sz-minus">A−</button>
|
||||
<button class="btn secondary" id="sz-plus">A+</button>
|
||||
</div>
|
||||
<div class="row" style="margin-bottom:8px">
|
||||
<button class="btn secondary" id="align-pos"
|
||||
title="Dieses Element auf allen Slides an dieselbe Stelle">Position angleichen</button>
|
||||
<button class="btn secondary" id="align-size"
|
||||
title="Dieses Element auf allen Slides gleich groß">Größe angleichen</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn secondary" id="reset-one">Element zurück</button>
|
||||
</div>
|
||||
<div class="hint" style="margin-top:8px">Ein angeklicktes Element öffnet
|
||||
sein eigenes Feld rechts.</div>
|
||||
<button class="btn secondary" id="reset-all" style="width:100%;margin-top:8px">Alles auf Standard</button>
|
||||
</details>
|
||||
|
||||
@@ -982,6 +996,14 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside class="panel panel-rechts" id="element-panel">
|
||||
<div class="el-kopf">
|
||||
<h1 id="el-titel">Element</h1>
|
||||
<button class="el-zu" id="el-zu" title="Auswahl aufheben">✕</button>
|
||||
</div>
|
||||
<div id="el-inhalt"></div>
|
||||
</aside>
|
||||
|
||||
<script src="./vendor/html-to-image.js"></script>
|
||||
<script src="./vendor/jszip.min.js"></script>
|
||||
<script>
|
||||
@@ -1785,7 +1807,7 @@
|
||||
content.className = "content";
|
||||
page.appendChild(content);
|
||||
|
||||
if (sl.type === "feature") {
|
||||
if (sl.type === "feature" && assets[sl.img]) {
|
||||
const mock = document.createElement("div");
|
||||
mock.className = "mock";
|
||||
if (sl.mockW) mock.style.setProperty("--mock-w", sl.mockW + "px");
|
||||
@@ -1829,7 +1851,10 @@
|
||||
/* Ein zweites Bild gibt es auf jeder Slide, nicht nur auf Feature-Slides:
|
||||
ein importiertes Bild soll auf der gewaehlten Slide landen, egal
|
||||
welche das ist. */
|
||||
if (sl.media2) {
|
||||
// Ein Bild, das es nicht mehr gibt (geloescht, nicht mitgepusht), darf die
|
||||
// Slide nicht mit einem leeren <img> vergiften: das faellt sonst erst im
|
||||
// Export auf, und zwar mit "1 Bild(er) nicht geladen".
|
||||
if (sl.media2 && assets[sl.media2.img]) {
|
||||
const m2 = sl.media2;
|
||||
const el2 = document.createElement("div");
|
||||
el2.className = "mock mock-2";
|
||||
@@ -2799,12 +2824,14 @@
|
||||
show(Number(holder.dataset.n));
|
||||
pick(idx, key);
|
||||
|
||||
// Den Maßstab nehmen, mit dem fitStrip gerechnet hat, nicht das Rechteck
|
||||
// messen: .strip ist ein Flex-Kind der schon skalierten Hülle, seine
|
||||
// Border-Box misst deshalb Breite mal Maßstab hoch zwei. Geteilt wurde
|
||||
// damit durch das Quadrat — bei eingepasster Ansicht wanderte das Element
|
||||
// um ein Vielfaches der Mausbewegung.
|
||||
const k = zoom || autoFit();
|
||||
/* Den Maßstab aus dem lesen, was tatsächlich gezeichnet ist. Das Rechteck
|
||||
zu messen geht nicht: .strip ist ein Flex-Kind der schon skalierten
|
||||
Hülle, seine Border-Box misst Breite mal Maßstab hoch zwei. Und
|
||||
autoFit() neu zu rechnen geht seit dem Element-Feld auch nicht mehr:
|
||||
das fährt beim Anklicken ein, macht die Bühne schmaler und ändert den
|
||||
Maßstab mitten im Klick. */
|
||||
const mSc = /scale\(([-\d.]+)\)/.exec(deck.style.transform || "");
|
||||
const k = mSc ? Number(mSc[1]) : (zoom || autoFit());
|
||||
const set = (edits[idx][key] = edits[idx][key] || {});
|
||||
const startX = e.clientX, startY = e.clientY;
|
||||
const baseX = set.dx || 0, baseY = set.dy || 0;
|
||||
@@ -2918,77 +2945,172 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Welches Textfeld der Slide haengt an welchem Element. Nur was hier steht,
|
||||
laesst sich im Element-Feld direkt beschriften; der Rest (Logos, Bilder,
|
||||
Geraete) traegt keinen Text. */
|
||||
const TEXT_ZU_FELD = {
|
||||
"badge": "eyebrow", "headline": "head", "body": "body",
|
||||
"title-head": "head", "title-sub": "sub", "wabe-claim": "sub",
|
||||
};
|
||||
|
||||
/* Ein geloeschtes Element laesst sich nicht mehr anklicken. Ohne diese
|
||||
Liste bliebe nur "Alles auf Standard" oder Rueckgaengig -- es waere also
|
||||
praktisch verloren. Sie steht deshalb immer da, nicht nur im Leerzustand. */
|
||||
function verstecktHtml() {
|
||||
const versteckt = Object.keys(edits[current] || {})
|
||||
.filter(k => (edits[current][k] || {}).weg);
|
||||
praktisch verloren. */
|
||||
/* Der Index kommt von aussen: seit jede Slide das ganze Blatt traegt, ist
|
||||
die angezeigte Slide nicht mehr zwingend die bearbeitete. */
|
||||
function verstecktHtml(i) {
|
||||
const versteckt = Object.keys(edits[i] || {})
|
||||
.filter(k => (edits[i][k] || {}).weg);
|
||||
if (!versteckt.length) return "";
|
||||
return '<div class="hint" style="margin-top:10px">Gelöscht auf dieser Slide:</div>' +
|
||||
'<div class="opts" style="margin-top:4px">' +
|
||||
versteckt.map(k => '<button data-zurueck="' + k + '">' +
|
||||
return '<div class="abschnitt">Gelöscht auf Slide ' + (i + 1) + '</div>' +
|
||||
'<div class="opts">' +
|
||||
versteckt.map(k => '<button data-zurueck="' + k + '" data-slide="' + i + '">' +
|
||||
((STANDARD[k] || {}).label || k) + ' zurück</button>').join("") + '</div>';
|
||||
}
|
||||
|
||||
function syncEditPanel() {
|
||||
const box = document.getElementById("edit-info");
|
||||
if (!picked) {
|
||||
box.dataset.for = "";
|
||||
box.innerHTML = '<span class="dim">Nichts ausgewählt</span>' + verstecktHtml();
|
||||
return;
|
||||
/* Ausrichten an der Slide. Gemessen statt gerechnet: die Mockups stehen
|
||||
perspektivisch im Raum und das Blatt ist gepolstert, ein Dreisatz ueber
|
||||
left und top laege daneben. 92 px ist der Rand, den die Gestaltung
|
||||
ohnehin haelt (.content). */
|
||||
const RAND = 92;
|
||||
function ausrichten(i, key, waag, senk) {
|
||||
const node = deck.querySelector(
|
||||
'.page[data-j="' + i + '"] [data-edit="' + key + '"]');
|
||||
const slide = node && node.closest(".slide");
|
||||
if (!slide) return;
|
||||
const sr = slide.getBoundingClientRect(), nr = node.getBoundingClientRect();
|
||||
if (!sr.width || !nr.width) return;
|
||||
const k = sr.width / 1080;
|
||||
const l = (nr.left - sr.left) / k, o = (nr.top - sr.top) / k;
|
||||
const b = nr.width / k, h = nr.height / k;
|
||||
const set = (edits[i][key] = edits[i][key] || {});
|
||||
if (waag) {
|
||||
const ziel = waag === "links" ? RAND
|
||||
: waag === "mitte" ? (1080 - b) / 2
|
||||
: 1080 - RAND - b;
|
||||
set.dx = Math.round((set.dx || 0) + (ziel - l));
|
||||
}
|
||||
if (senk) {
|
||||
const ziel = senk === "oben" ? RAND
|
||||
: senk === "mitte" ? (1350 - h) / 2
|
||||
: 1350 - RAND - h;
|
||||
set.dy = Math.round((set.dy || 0) + (ziel - o));
|
||||
}
|
||||
applyEditsAll();
|
||||
syncEditPanel();
|
||||
saveState();
|
||||
}
|
||||
|
||||
function syncEditPanel() {
|
||||
const koerper = document.getElementById("el-inhalt");
|
||||
const titel = document.getElementById("el-titel");
|
||||
// Nur beim Auf- und Zugehen neu einpassen: sonst springt der Streifen bei
|
||||
// jedem Wechsel der Auswahl.
|
||||
const vorher = document.body.dataset.elem;
|
||||
document.body.dataset.elem = picked ? "an" : "aus";
|
||||
const wechsel = vorher !== document.body.dataset.elem;
|
||||
if (!picked) { koerper.dataset.for = ""; if (wechsel) fitStrip(); return; }
|
||||
|
||||
const std = STANDARD[picked.key] || {};
|
||||
const e = (edits[picked.slide] || {})[picked.key] || {};
|
||||
const base = stdSize(picked.key, picked.slide);
|
||||
const id = picked.slide + ":" + picked.key;
|
||||
const feld = TEXT_ZU_FELD[picked.key];
|
||||
const id = picked.slide + ":" + picked.key + ":" + (edits[picked.slide][picked.key] || {}).weg;
|
||||
// Nur bei einer anderen Auswahl neu aufbauen. Beim Ziehen laeuft das hier
|
||||
// pro Mausbewegung; ein Neuaufbau wuerde die Eingabe unter den Fingern
|
||||
// wegreissen.
|
||||
if (box.dataset.for !== id) {
|
||||
box.dataset.for = id;
|
||||
box.innerHTML =
|
||||
'<b>' + (std.label || picked.key) + '</b> · Slide ' + (picked.slide + 1) +
|
||||
if (koerper.dataset.for !== id) {
|
||||
koerper.dataset.for = id;
|
||||
titel.textContent = (std.label || picked.key);
|
||||
koerper.innerHTML =
|
||||
'<p class="sub">Slide ' + (picked.slide + 1) + " von " + SLIDES.length + '</p>' +
|
||||
(feld
|
||||
? '<div class="abschnitt">Text</div>' +
|
||||
'<textarea id="f-text" rows="3"></textarea>' +
|
||||
'<div class="hint">*Wort* setzt den Akzent, Zeilenumbruch bricht um.</div>'
|
||||
: "") +
|
||||
'<div class="abschnitt">Maße</div>' +
|
||||
'<div class="fields">' +
|
||||
(std.prop
|
||||
? '<label for="f-size">' + (std.prop === "font-size" ? "Schrift" : "Breite") +
|
||||
'</label><input id="f-size" type="number" step="1" style="grid-column:2/5">'
|
||||
: '') +
|
||||
: "") +
|
||||
'<label for="f-dx">X</label><input id="f-dx" type="number" step="1">' +
|
||||
'<label for="f-dy">Y</label><input id="f-dy" type="number" step="1">' +
|
||||
'<label for="f-z">Ebene</label>' +
|
||||
'<input id="f-z" type="number" step="1" placeholder="Standard" style="grid-column:2/5">' +
|
||||
'</div>' +
|
||||
'<div class="opts" style="margin-top:8px"><button id="f-weg">Element löschen</button></div>' +
|
||||
'<div class="hint">' + (std.prop ? 'Standard ' + base + ' px · ' : '') +
|
||||
'Obere linke Ecke, 0 / 0 ist die Mitte der Slide.<br>' +
|
||||
'Ebene wie z-index: höher liegt weiter vorn, leer heißt Standard.</div>' +
|
||||
verstecktHtml();
|
||||
'<div class="hint">' + (std.prop ? "Standard " + base + " px · " : "") +
|
||||
'Obere linke Ecke, 0 / 0 ist die Mitte der Slide. Ebene wie z-index, ' +
|
||||
'leer heißt Standard.</div>' +
|
||||
(std.prop
|
||||
? '<div class="row" style="margin-top:8px">' +
|
||||
'<button class="btn secondary" id="sz-minus">A−</button>' +
|
||||
'<button class="btn secondary" id="sz-plus">A+</button></div>'
|
||||
: "") +
|
||||
'<div class="abschnitt">Ausrichten an der Slide</div>' +
|
||||
'<div class="opts">' +
|
||||
'<button data-waag="links">Links</button>' +
|
||||
'<button data-waag="mitte">Mitte</button>' +
|
||||
'<button data-waag="rechts">Rechts</button></div>' +
|
||||
'<div class="opts" style="margin-top:4px">' +
|
||||
'<button data-senk="oben">Oben</button>' +
|
||||
'<button data-senk="mitte">Mitte</button>' +
|
||||
'<button data-senk="unten">Unten</button></div>' +
|
||||
'<div class="abschnitt">Auf allen Slides</div>' +
|
||||
'<div class="row">' +
|
||||
'<button class="btn secondary" id="align-pos" title="Dieses Element auf allen Slides an dieselbe Stelle">Position angleichen</button>' +
|
||||
'<button class="btn secondary" id="align-size" title="Dieses Element auf allen Slides gleich groß">Größe angleichen</button>' +
|
||||
'</div>' +
|
||||
'<div class="abschnitt">Element</div>' +
|
||||
'<div class="row">' +
|
||||
'<button class="btn secondary" id="reset-one">Zurücksetzen</button>' +
|
||||
'<button class="btn secondary" id="f-weg">Löschen</button>' +
|
||||
'</div>' +
|
||||
verstecktHtml(picked.slide);
|
||||
if (wechsel) fitStrip();
|
||||
}
|
||||
const put = (sel, v) => {
|
||||
const el = box.querySelector(sel);
|
||||
const el = koerper.querySelector(sel);
|
||||
if (el && document.activeElement !== el) el.value = v;
|
||||
};
|
||||
if (feld) put("#f-text", htmlZuText(SLIDES[picked.slide][feld]));
|
||||
if (std.prop) put("#f-size", e.size || base);
|
||||
put("#f-z", e.z == null ? "" : e.z);
|
||||
const c = centreOf(picked.slide, picked.key);
|
||||
if (c) { put("#f-dx", c.x); put("#f-dy", c.y); }
|
||||
}
|
||||
|
||||
document.getElementById("edit-info").addEventListener("click", ev => {
|
||||
document.getElementById("el-zu").addEventListener("click", () => {
|
||||
picked = null;
|
||||
deck.querySelectorAll("[data-edit]").forEach(n => n.classList.remove("picked"));
|
||||
syncEditPanel();
|
||||
});
|
||||
|
||||
document.getElementById("el-inhalt").addEventListener("click", ev => {
|
||||
const zurueck = ev.target.closest("[data-zurueck]");
|
||||
if (zurueck) {
|
||||
const k = zurueck.dataset.zurueck;
|
||||
const set = (edits[current] || {})[k];
|
||||
const i = Number(zurueck.dataset.slide);
|
||||
const set = (edits[i] || {})[k];
|
||||
if (set) delete set.weg;
|
||||
applyEditsAll();
|
||||
document.getElementById("edit-info").dataset.for = "";
|
||||
pick(current, k);
|
||||
document.getElementById("el-inhalt").dataset.for = "";
|
||||
pick(i, k);
|
||||
saveState();
|
||||
setStatus(((STANDARD[k] || {}).label || k) + " ist wieder da", "ok");
|
||||
return;
|
||||
}
|
||||
if (!ev.target.closest("#f-weg") || !picked) return;
|
||||
const b = ev.target.closest("button");
|
||||
if (!b || !picked) return;
|
||||
if (b.id === "sz-plus") { resize(1); return; }
|
||||
if (b.id === "sz-minus") { resize(-1); return; }
|
||||
if (b.id === "reset-one") { resetOne(); return; }
|
||||
if (b.id === "align-pos") { alignAll("pos"); return; }
|
||||
if (b.id === "align-size") { alignAll("size"); return; }
|
||||
if (b.dataset.waag) { ausrichten(picked.slide, picked.key, b.dataset.waag, null); return; }
|
||||
if (b.dataset.senk) { ausrichten(picked.slide, picked.key, null, b.dataset.senk); return; }
|
||||
if (b.id !== "f-weg") return;
|
||||
const i = picked.slide, key = picked.key;
|
||||
if (key === "mock-2") {
|
||||
// Beim zweiten Bild wirklich entfernen, nicht nur ausblenden: sonst
|
||||
@@ -3004,7 +3126,7 @@
|
||||
applyEditsAll();
|
||||
// Ausgewaehlt lassen: dann steht "Element zurueck" gleich bereit. Der
|
||||
// Aufbau muss aber erzwungen werden, sonst fehlt die Rueckhol-Liste.
|
||||
document.getElementById("edit-info").dataset.for = "";
|
||||
document.getElementById("el-inhalt").dataset.for = "";
|
||||
syncEditPanel();
|
||||
}
|
||||
saveState();
|
||||
@@ -3013,9 +3135,24 @@
|
||||
|
||||
/* Eingabe wirkt sofort. Kein pick() danach: das baut das Feld neu auf und
|
||||
der Fokus waere weg. */
|
||||
document.getElementById("edit-info").addEventListener("input", ev => {
|
||||
document.getElementById("el-inhalt").addEventListener("input", ev => {
|
||||
const el = ev.target;
|
||||
if (!picked || !el.matches("input")) return;
|
||||
// textarea zaehlt nicht als input: ohne das faellt das Textfeld durch.
|
||||
if (!picked || !el.matches("input, textarea")) return;
|
||||
/* Der Text gehoert der Slide, nicht der Bearbeitung: er wandert in
|
||||
dieselbe Ueberschreibung, die auch das Inhalt-Feld links beschreibt. */
|
||||
if (el.id === "f-text") {
|
||||
const feld = TEXT_ZU_FELD[picked.key];
|
||||
if (!feld) return;
|
||||
werteVon(picked.slide)[feld] = textZuHtml(el.value);
|
||||
slidesNeuAufbauen();
|
||||
rebuildPage(picked.slide);
|
||||
applyEditsAll();
|
||||
pick(picked.slide, picked.key);
|
||||
syncInhalt();
|
||||
saveState();
|
||||
return;
|
||||
}
|
||||
const raw = el.value.trim();
|
||||
const n = Number(raw);
|
||||
if (raw !== "" && !Number.isFinite(n)) return;
|
||||
@@ -3070,9 +3207,7 @@
|
||||
syncEditPanel();
|
||||
}
|
||||
});
|
||||
document.getElementById("sz-plus").addEventListener("click", () => resize(1));
|
||||
document.getElementById("sz-minus").addEventListener("click", () => resize(-1));
|
||||
document.getElementById("reset-one").addEventListener("click", resetOne);
|
||||
|
||||
/* ─── Angleichen über alle Slides ────────────────────────────────────────
|
||||
Das gewählte Element ist die Vorlage, alle gleichnamigen auf den anderen
|
||||
Slides ziehen nach. Angeglichen wird die Stelle, nicht der Versatz: jedes
|
||||
@@ -3114,8 +3249,7 @@
|
||||
setStatus(label + ": " + (was === "size" ? "Größe" : "Position") +
|
||||
" auf " + n + " weitere Slide" + (n === 1 ? "" : "s") + " übertragen", "ok");
|
||||
}
|
||||
document.getElementById("align-pos").addEventListener("click", () => alignAll("pos"));
|
||||
document.getElementById("align-size").addEventListener("click", () => alignAll("size"));
|
||||
|
||||
|
||||
/* ─── Merkzettel ─────────────────────────────────────────────────────────
|
||||
Der gemerkte Stand liegt als eigenes Projekt in state/, also im Repo und
|
||||
@@ -3453,6 +3587,22 @@
|
||||
await bilderNachladen();
|
||||
await preloadAssets();
|
||||
const restored = await loadState();
|
||||
/* Verweise auf Bilder, die es nicht mehr gibt, hier aufloesen statt sie
|
||||
bis in den Export zu schleppen. Sie entstehen, wenn eine importierte
|
||||
Datei geloescht oder nicht mitgepusht wurde. */
|
||||
const verloren = [];
|
||||
slideListe.forEach((e, i) => {
|
||||
const w = e.werte || {};
|
||||
if (w.media2 && w.media2.img && !assets[w.media2.img]) {
|
||||
verloren.push(w.media2.img.split("/").pop());
|
||||
w.media2 = null;
|
||||
}
|
||||
if (w.img && !assets[w.img]) { verloren.push(w.img.split("/").pop()); delete w.img; }
|
||||
});
|
||||
if (verloren.length) {
|
||||
slidesNeuAufbauen();
|
||||
saveState();
|
||||
}
|
||||
buildDeck();
|
||||
deck.dataset.view = view;
|
||||
press("variants", "v", variant);
|
||||
@@ -3464,7 +3614,10 @@
|
||||
press("views", "w", view);
|
||||
anzahlNachziehen();
|
||||
show(current);
|
||||
if (restored) setStatus("Projektstand geladen", "ok");
|
||||
if (verloren.length)
|
||||
setStatus("Fehlende Bilder entfernt: " + verloren.join(", ") +
|
||||
" · bitte neu importieren", "err");
|
||||
else if (restored) setStatus("Projektstand geladen", "ok");
|
||||
await document.fonts.ready;
|
||||
fontEmbedCss = await htmlToImage.getFontEmbedCSS(deck.querySelector(".slide"));
|
||||
["dl-png", "dl-all"].forEach(id => document.getElementById(id).disabled = false);
|
||||
|
||||
+34
-2
@@ -23,6 +23,34 @@
|
||||
],
|
||||
"continuous": "an",
|
||||
"current": 0,
|
||||
"deck": [
|
||||
{
|
||||
"ref": "titel",
|
||||
"werte": {
|
||||
"media2": {
|
||||
"ar": 0.4603,
|
||||
"bottom": 400,
|
||||
"img": "./assets/eigene/Bildschirmfoto-2026-05-16-um-17.07.04.png",
|
||||
"left": 300,
|
||||
"rot": 0,
|
||||
"w": 480,
|
||||
"z": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ref": "workflow-engine"
|
||||
},
|
||||
{
|
||||
"ref": "projektmanagement"
|
||||
},
|
||||
{
|
||||
"ref": "chat"
|
||||
},
|
||||
{
|
||||
"ref": "form-builder"
|
||||
}
|
||||
],
|
||||
"edits": [
|
||||
{
|
||||
"laptop": {
|
||||
@@ -34,6 +62,10 @@
|
||||
"dx": -40,
|
||||
"dy": 597
|
||||
},
|
||||
"mock-2": {
|
||||
"dx": 1188,
|
||||
"dy": 270
|
||||
},
|
||||
"szene": {
|
||||
"dx": 129,
|
||||
"dy": -286,
|
||||
@@ -160,7 +192,7 @@
|
||||
"superSample": 3,
|
||||
"tagOut": "an",
|
||||
"tilt": "aus",
|
||||
"v": 5,
|
||||
"v": 6,
|
||||
"variant": "hell",
|
||||
"view": "einzeln"
|
||||
"view": "streifen"
|
||||
}
|
||||
Reference in new issue
Block a user