group editor code display changed
Sonarqube Scanner / Prepare environment (push) Successful in 6s
Sonarqube Scanner / Build and test (push) Successful in 22s
Sonarqube Scanner / SonarQube analysis (push) Successful in 56s
Sonarqube Scanner / Deploy to Server (push) Successful in 6s

This commit is contained in:
Florian Zumpe
2026-10-18 23:03:12 +02:00
parent 04e05ecfd4
commit a66a795407
10 changed files with 109 additions and 111 deletions
+8 -8
View File
@@ -595,24 +595,24 @@
.current-location-button img { width: 24px; height: 24px; vertical-align: middle; }
/* Shared drafts and vertically scrolling Wichtel-code wheels. */
.group-share-pictures { display: flex; align-items: center; justify-content: center; gap: 1rem; margin: 1rem 0; }
.group-share-pictures img { width: min(34vw, 9rem); height: 10rem; object-fit: contain; }
.group-share-button img { display: block; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
.group-dialog { width: min(35rem, calc(100% - 2rem)); max-height: 90dvh; box-sizing: border-box; overflow-y: auto; padding: 1.25rem; border: 1px solid #c8d3ca; border-radius: 1.25rem; background: #f8faf5; color: #1f3022; }
.group-code-dialog { width: min(56rem, calc(100vw - 1rem)); max-height: calc(100dvh - 1rem); padding: clamp(.5rem, 2vw, 1.5rem); }
.group-dialog::backdrop { background: rgba(20, 40, 25, .5); }
.group-dialog-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.group-dialog-header h2 { margin: 0; font-size: 1.2rem; }
.group-dialog button { min-width: 44px; min-height: 44px; border: 1px solid #c8d3ca; border-radius: .85rem; background: white; color: #24462b; touch-action: manipulation; }
.group-dialog button:focus-visible, .group-wheel-viewport:focus-visible { outline: 3px solid #1e6fba; outline-offset: 2px; }
.group-wheels { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .3rem; padding: 0; border: 0; margin: 1rem 0; }
.group-wheels { display: grid; grid-template-columns: repeat(5, minmax(44px, 1fr)); gap: .3rem; padding: 0; border: 0; margin: 1rem 0; }
.group-code-fieldset { border: 0; padding: 0; min-width: 0; }
.group-code-fieldset legend { margin-bottom: .5rem; font-weight: 600; }
.group-wheel { display: grid; gap: .3rem; min-width: 0; }
.group-wheel-viewport { height: 156px; padding: 52px 0; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; border-radius: .85rem; background: linear-gradient(to bottom, #edf2eb 0 33.33%, #dcebdc 33.33% 66.66%, #edf2eb 66.66%); }
.group-wheel-viewport { height: 288px; padding: 96px 0; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; border-radius: .85rem; background: linear-gradient(to bottom, #edf2eb 0 33.33%, #dcebdc 33.33% 66.66%, #edf2eb 66.66%); }
.group-wheel-viewport::-webkit-scrollbar { display: none; }
.group-wheel-option { height: 52px; display: grid; place-items: center; scroll-snap-align: center; cursor: pointer; }
.group-wheel-option img { width: 100%; max-width: 48px; height: 48px; object-fit: contain; pointer-events: none; }
.group-code { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .3rem; padding: .6rem; border: 1px solid #c8d3ca; border-radius: 1rem; background: #e7f1e9; }
.group-code img { width: 100%; height: 64px; object-fit: contain; }
.group-wheel-option { height: 96px; display: grid; place-items: center; scroll-snap-align: center; cursor: pointer; }
.group-wheel-option img { width: 100%; max-width: 88px; height: 88px; object-fit: contain; pointer-events: none; }
.group-code { display: grid; grid-template-columns: repeat(5, minmax(44px, 1fr)); gap: .3rem; padding: .6rem; border: 1px solid #c8d3ca; border-radius: 1rem; background: #e7f1e9; }
.group-code img { width: 100%; height: clamp(64px, 14vw, 128px); object-fit: contain; }
.group-actions { max-width: min(18rem, 52vw); flex-wrap: wrap; justify-content: flex-end; }
.group-actions .editor-secondary-button { font-size: .85rem; }
.group-status { display: block; min-height: 1.5rem; color: #9b2c2c; }
+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none" stroke="#315d3a" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 23 12 6Q14 2 16 6L23 23Z" fill="#e7f1e9"/>
<path d="M7 24Q5 34 14 43 23 34 21 24" fill="#fff"/>
<circle cx="14" cy="25" r="3" fill="#f2ca6b"/>
<path d="M25 25 32 10Q34 6 36 10L43 25Z" fill="#f8f5eb"/>
<path d="M27 26Q25 35 34 43 43 35 41 26" fill="#fff"/>
<circle cx="34" cy="27" r="3" fill="#f2ca6b"/>
</svg>

After

Width:  |  Height:  |  Size: 507 B

+13 -17
View File
@@ -579,33 +579,29 @@
<script src="js/user-messages.js?v=0.12.14"></script>
<script src="js/local-routes.js?v=0.12.14"></script>
<script src="js/route-recorder.js?v=0.12.14"></script>
<dialog id="group-share-dialog" class="group-dialog" aria-labelledby="group-share-title">
<dialog id="group-entry-dialog" class="group-dialog group-code-dialog" aria-labelledby="group-entry-title">
<header class="group-dialog-header">
<h2 id="group-share-title" data-i18n="groups.share">Gemeinsam bearbeiten</h2>
<button id="group-share-close" type="button" data-i18n-aria-label="info.close">×</button>
</header>
<p id="group-share-route"></p>
<div id="group-share-pictures" class="group-share-pictures"></div>
<p data-i18n="groups.share-help">Mit diesen beiden Wichteln öffnest du einen gemeinsamen Entwurf. Andere können mit einem Wichtelcode mitmachen. Das Original bleibt erhalten.</p>
<div class="editor-form-actions">
<button id="group-share-start" type="button" data-i18n="groups.share-start">Gemeinsamen Entwurf öffnen</button>
</div>
</dialog>
<dialog id="group-dialog" class="group-dialog" aria-labelledby="group-title">
<header class="group-dialog-header">
<h2 id="group-title" data-i18n="groups.share">Gemeinsam bearbeiten</h2>
<button id="group-close" type="button" aria-label="Schließen" data-i18n-aria-label="info.close">×</button>
<h2 id="group-entry-title" data-i18n="groups.join-open">Gruppencode eingeben</h2>
<button id="group-entry-close" type="button" data-i18n-aria-label="info.close">×</button>
</header>
<section id="group-entry">
<p data-i18n="groups.wheel-help">Schiebe die Wichtel nach oben oder unten. Die fünf Wichtel in der Mitte ergeben den Code. Lies von links nach rechts. Ein Wichtel darf mehrmals vorkommen.</p>
<fieldset class="group-code-fieldset"><legend data-i18n="groups.code-label">Gruppencode</legend><div id="group-wheels" class="group-wheels"></div></fieldset>
<button id="group-join" type="button" data-i18n="groups.join">Mitmachen</button>
<div class="editor-form-actions"><button id="group-join" type="button" data-i18n="groups.join">Mitmachen</button></div>
</section>
<output id="group-entry-status" class="group-status"></output>
</dialog>
<dialog id="group-dialog" class="group-dialog group-code-dialog" aria-labelledby="group-title">
<header class="group-dialog-header">
<h2 id="group-title" data-i18n="groups.code-label">Gruppencode</h2>
<button id="group-close" type="button" aria-label="Schließen" data-i18n-aria-label="info.close">×</button>
</header>
<p id="group-route"></p>
<section id="group-invitation" hidden>
<p data-i18n="groups.invite-help">Zeige diesen Code den anderen. Damit können sie deinen gemeinsamen Entwurf öffnen und bearbeiten. Das Original bleibt erhalten.</p>
<div id="group-code" class="group-code" aria-label="Gruppencode" data-i18n-aria-label="groups.code-label"></div>
<p id="group-expiry"></p>
<button id="group-edit" type="button" data-i18n="groups.edit">Entwurf bearbeiten</button>
<div class="editor-form-actions"><button id="group-edit" type="button" data-i18n="groups.edit">Entwurf bearbeiten</button></div>
</section>
<output id="group-status" class="group-status"></output>
</dialog>
-1
View File
@@ -1652,7 +1652,6 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
if (shareInProgress) return;
shareInProgress = true;
try {
if (ns.Groups.confirmShare && !await ns.Groups.confirmShare(routeName(route))) return;
let sourceId = routeId(route);
if (routeSource(route) === 'local') {
if (!globalThis.confirm(ns.I18n.t('groups.upload-confirm'))) return;
+28 -43
View File
@@ -6,10 +6,11 @@ globalThis.Wegwichtel ??= {};
joinOpen: 'group-join-open', editorJoin: 'editor-group-join', list: 'editor-group-list',
dialog: 'group-dialog', close: 'group-close', status: 'group-status', wheels: 'group-wheels',
entry: 'group-entry', invitation: 'group-invitation', code: 'group-code', join: 'group-join',
edit: 'group-edit', expiry: 'group-expiry'
edit: 'group-edit', expiry: 'group-expiry',
entryDialog: 'group-entry-dialog', entryClose: 'group-entry-close', entryStatus: 'group-entry-status'
};
const codeLength = 5;
const wheelStep = 52;
const wheelStep = 96;
const revisions = new Map();
let selection = [];
let currentGroup = null;
@@ -73,12 +74,12 @@ globalThis.Wegwichtel ??= {};
async function guarded(action) {
if (pending) return;
pending = true;
element(ids.status).textContent = '';
const status = element(element(ids.entryDialog).open ? ids.entryStatus : ids.status);
status.textContent = '';
element(ids.join).disabled = true;
try { await action(); } catch (error) {
element(ids.status).textContent = errorMessage(error);
if (!element(ids.dialog).open) {
element(ids.entry).hidden = true;
element(element(ids.entryDialog).open ? ids.entryStatus : ids.status).textContent = errorMessage(error);
if (!element(ids.dialog).open && !element(ids.entryDialog).open) {
element(ids.invitation).hidden = true;
element(ids.dialog).showModal();
}
@@ -146,21 +147,22 @@ globalThis.Wegwichtel ??= {};
}
function openEntry() {
if (element(ids.dialog).open) element(ids.dialog).close();
dialogGeneration += 1;
selection = new Array(codeLength).fill(0);
element(ids.status).textContent = '';
element(ids.entry).hidden = false;
element(ids.invitation).hidden = true;
element(ids.entryStatus).textContent = '';
const wheels = element(ids.wheels);
wheels.replaceChildren(...selection.map((_, index) => makeWheel(index)));
if (!element(ids.dialog).open) element(ids.dialog).showModal();
if (!element(ids.entryDialog).open) element(ids.entryDialog).showModal();
}
function showInvitation(payload) {
if (!Array.isArray(payload.code) || payload.code.length !== codeLength) throw new Error(t('error.connection'));
currentGroup = payload.group;
element(ids.status).textContent = '';
element(ids.entry).hidden = true;
if (element(ids.entryDialog).open) element(ids.entryDialog).close();
element(ids.invitation).hidden = false;
element('group-route').textContent = payload.group.route.name || '';
const motifs = payload.code.map(value => ns.GroupSymbols.get(value));
element(ids.code).replaceChildren(...motifs.map(picture));
const time = new Date(payload.expiresAt * 1000).toLocaleTimeString(ns.I18n.language, { hour: '2-digit', minute: '2-digit' });
@@ -182,7 +184,8 @@ globalThis.Wegwichtel ??= {};
finally { invitationPolling = false; }
}
async function edit(group) {
element(ids.dialog).close();
if (element(ids.dialog).open) element(ids.dialog).close();
if (element(ids.entryDialog).open) element(ids.entryDialog).close();
await ns.EditorUi.openGroup(group.route);
}
@@ -258,34 +261,20 @@ globalThis.Wegwichtel ??= {};
function shareButton(action) {
const result = document.createElement('button');
result.type = 'button';
result.className = 'editor-icon-button';
result.className = 'editor-icon-button group-share-button';
result.dataset.editorAction = action;
result.title = t('share');
result.setAttribute('aria-label', t('share'));
result.setAttribute('aria-haspopup', 'dialog');
result.textContent = '👥';
const icon = document.createElement('img');
icon.src = '/images/icons/group-edit.svg';
icon.alt = '';
icon.width = 32;
icon.height = 32;
result.appendChild(icon);
return result;
}
let shareDecision = null;
function confirmShare(name) {
if (shareDecision) return Promise.resolve(false);
const dialog = element('group-share-dialog');
element('group-share-route').textContent = name;
element('group-share-pictures').replaceChildren(...['🐦', '🪨'].map(value => picture(ns.GroupSymbols.get(value))));
return new Promise(resolve => {
shareDecision = resolve;
dialog.showModal();
});
}
function finishShare(accepted) {
const resolve = shareDecision;
shareDecision = null;
element('group-share-dialog').close();
resolve?.(accepted);
}
function headersFor(path) {
const id = /^\/routes\/(\d+)(?:\/|$)/.exec(path)?.[1];
return id && revisions.has(id) ? { 'If-Match': String(revisions.get(id)) } : {};
@@ -306,31 +295,27 @@ globalThis.Wegwichtel ??= {};
code: selection.map(index => motifs[index].unicode), creator_id: ns.LocalRoutes.creatorId()
});
} catch (error) {
if (!element(ids.dialog).open || generation !== dialogGeneration) return;
if (!element(ids.entryDialog).open || generation !== dialogGeneration) return;
throw error;
}
if (!element(ids.dialog).open || generation !== dialogGeneration) return;
if (!element(ids.entryDialog).open || generation !== dialogGeneration) return;
await edit(payload.group);
await refresh();
}
function init() {
globalThis.setInterval?.(() => { refreshInvitation(); }, 30_000);
activate(element('group-share-start'), () => finishShare(true));
activate(element('group-share-close'), () => finishShare(false));
element('group-share-dialog').addEventListener('close', () => {
const resolve = shareDecision; shareDecision = null; resolve?.(false);
});
document.addEventListener('wegwichtel:point-drafts-changed', () => { renderPending(); ns.StorageUsage?.refresh(); });
element(ids.dialog).addEventListener('cancel', () => { dialogGeneration += 1; });
element(ids.dialog).addEventListener('close', () => { dialogGeneration += 1; });
element(ids.entryDialog).addEventListener('cancel', () => { dialogGeneration += 1; });
element(ids.entryDialog).addEventListener('close', () => { dialogGeneration += 1; });
activate(element(ids.entryClose), () => element(ids.entryDialog).close());
for (const id of [ids.joinOpen, ids.editorJoin]) activate(element(id), openEntry);
activate(element(ids.close), () => element(ids.dialog).close());
activate(element(ids.join), () => guarded(joinSelectedGroup));
activate(element(ids.edit), () => guarded(() => edit(currentGroup)));
}
ns.Groups = Object.freeze({ share, shareButton, confirmShare, refresh, refreshInvitation, headersFor, observe });
ns.Groups = Object.freeze({ share, shareButton, refresh, refreshInvitation, headersFor, observe });
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true });
else init();
}(globalThis.Wegwichtel));
-2
View File
@@ -135,8 +135,6 @@ globalThis.Wegwichtel ??= {};
},
"groups": {
"code-wait": "In der Gruppe wird gerade eine Station bearbeitet. Ein neuer Code erscheint, sobald die Stationen wieder frei sind. Ein abgelaufener Code erlaubt keinen neuen Beitritt.",
"share-help": "Mit diesen beiden Wichteln öffnest du einen gemeinsamen Entwurf. Andere können mit einem Wichtelcode mitmachen. Das Original bleibt erhalten.",
"share-start": "Gemeinsamen Entwurf öffnen",
"title": "Gemeinsame Entwürfe",
"upload-confirm": "Dein Entwurf mit Bildern und Ton wird dafür an die Website gesendet und als gemeinsamer Entwurf kopiert. Fortfahren?",
"share": "Gemeinsam bearbeiten",
-2
View File
@@ -135,8 +135,6 @@ globalThis.Wegwichtel ??= {};
},
"groups": {
"code-wait": "Someone in the group is editing a stop. A new code will appear once the stops are free. An expired code cannot be used to join.",
"share-help": "These two gnomes open a shared draft. Others can join using a gnome code. The original stays unchanged.",
"share-start": "Open a shared draft",
"title": "Shared drafts",
"upload-confirm": "Your draft, including pictures and recordings, will be sent to the website and copied into a shared draft. Continue?",
"share": "Edit together",