editor overhaul
This commit is contained in:
+78
-16
@@ -368,6 +368,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const label = document.createElement('div');
|
||||
appendText(label, 'strong', routeName(route));
|
||||
appendText(label, 'small', routeSource(route) === 'local' ? ns.I18n.t("app.draft") : ns.I18n.t("editor.published-route"));
|
||||
if (routeSource(route) === 'local' && route.submissionStatus) {
|
||||
appendText(label, 'small', ns.I18n.t(`editor.${route.submissionStatus}`));
|
||||
}
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.className = classes.iconRow;
|
||||
@@ -458,6 +461,36 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
setStatus('');
|
||||
// Local drafts are usable even while this request is waiting for a connection.
|
||||
void refreshPublicRoutes(requestGeneration);
|
||||
void refreshSubmittedRoutes(requestGeneration);
|
||||
}
|
||||
|
||||
async function refreshSubmittedRoutes(requestGeneration) {
|
||||
const submitted = localRouteSummaries().filter(route => route.serverRouteId);
|
||||
await Promise.allSettled(submitted.map(async route => {
|
||||
const existing = await findSubmittedRoute(route.serverRouteId);
|
||||
if (requestGeneration !== routeListGeneration) return;
|
||||
const latest = ns.LocalRoutes.get(route.id);
|
||||
if (!latest || String(latest.route.serverRouteId) !== String(route.serverRouteId)) return;
|
||||
const submissionStatus = submittedStatus(existing);
|
||||
if ((latest.route.submissionStatus || null) === submissionStatus) return;
|
||||
latest.route.submissionStatus = submissionStatus;
|
||||
const saved = ns.LocalRoutes.save(latest);
|
||||
if (state.currentSource === 'local' && routeId(state.currentRoute) === route.id) {
|
||||
state.currentRoute.submissionStatus = submissionStatus;
|
||||
renderLocalSubmissionStatus(saved.route);
|
||||
}
|
||||
}));
|
||||
if (requestGeneration !== routeListGeneration) return;
|
||||
state.routes = [...localRouteSummaries(), ...state.routes.filter(route => routeSource(route) === 'server')];
|
||||
renderRouteList();
|
||||
}
|
||||
|
||||
function renderLocalSubmissionStatus(route) {
|
||||
const uploaded = Boolean(route?.serverRouteId);
|
||||
const uploadButton = byId(ids.routeUpload);
|
||||
if (uploadButton) uploadButton.textContent = ns.I18n.t(uploaded ? 'editor.submitted' : 'editor.submit');
|
||||
const hint = uploaded ? 'editor.submitted-hint' : 'editor.draft-hint';
|
||||
byId(ids.routeActivated).textContent = ns.I18n.t(route?.submissionStatus ? `editor.${route.submissionStatus}-hint` : hint);
|
||||
}
|
||||
|
||||
async function refreshPublicRoutes(requestGeneration) {
|
||||
@@ -532,13 +565,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
const uploadButton = byId(ids.routeUpload);
|
||||
if (state.currentSource === 'local') {
|
||||
const uploaded = Boolean(route?.serverRouteId);
|
||||
setHidden(uploadButton, false);
|
||||
setDisabled(uploadButton, false);
|
||||
if (uploadButton) uploadButton.textContent = uploaded ? ns.I18n.t("editor.submitted") : ns.I18n.t("editor.submit");
|
||||
byId(ids.routeActivated).textContent = uploaded
|
||||
? ns.I18n.t("editor.submitted-hint")
|
||||
: ns.I18n.t("editor.draft-hint");
|
||||
renderLocalSubmissionStatus(route);
|
||||
} else {
|
||||
setHidden(uploadButton, true);
|
||||
setDisabled(uploadButton, true);
|
||||
@@ -946,23 +975,56 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
return ns.LocalRoutes.save(routePackage);
|
||||
}
|
||||
|
||||
function submittedStatus(existing) {
|
||||
if (!existing) return 'removed';
|
||||
return existing.status === 'deleted' ? 'rejected' : null;
|
||||
}
|
||||
|
||||
async function findSubmittedRoute(id) {
|
||||
try {
|
||||
const payload = await readJson(`/routes/${encodeURIComponent(id)}?includeDeleted=true`);
|
||||
return payload.route || payload;
|
||||
} catch (error) {
|
||||
if (error.status === 404) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function clearSubmittedReferences(routePackage) {
|
||||
routePackage.route.serverRouteId = null;
|
||||
routePackage.route.uploadedAt = null;
|
||||
for (const poi of routePackage.pois || []) {
|
||||
delete poi.serverPoiId;
|
||||
delete poi.serverAudio;
|
||||
for (const image of poi.images || []) delete image.serverPictureId;
|
||||
}
|
||||
checkpointLocalUpload(routePackage);
|
||||
}
|
||||
|
||||
async function prepareServerRoute(routePackage) {
|
||||
const id = routePackage.route.serverRouteId;
|
||||
const replacedSubmission = Boolean(id || routePackage.route.submissionStatus);
|
||||
if (id) {
|
||||
const payload = await readJson(`/routes/${encodeURIComponent(id)}`);
|
||||
const existing = payload.route || payload;
|
||||
if (existing.activatedAt || existing.activated_at) {
|
||||
throw new Error(ns.I18n.t("editor.read-only-hint"));
|
||||
const existing = await findSubmittedRoute(id);
|
||||
if (!existing || existing.status === 'deleted') {
|
||||
// Preserve the old trash entry; submit a new draft with fresh references.
|
||||
routePackage.route.submissionStatus = submittedStatus(existing);
|
||||
clearSubmittedReferences(routePackage);
|
||||
} else {
|
||||
if (existing.activatedAt || existing.activated_at) {
|
||||
throw new Error(ns.I18n.t("editor.read-only-hint"));
|
||||
}
|
||||
await sendForm(`/routes/${encodeURIComponent(id)}`, 'PUT', localRouteUploadForm(routePackage));
|
||||
return { id, previousPois: existing.pois || [] };
|
||||
}
|
||||
await sendForm(`/routes/${encodeURIComponent(id)}`, 'PUT', localRouteUploadForm(routePackage));
|
||||
return { id, previousPois: existing.pois || [] };
|
||||
}
|
||||
const payload = await sendForm('/routes', 'POST', localRouteUploadForm(routePackage));
|
||||
const createdId = routeId(payload.route || payload);
|
||||
if (!createdId) throw new Error(ns.I18n.t("editor.error.send-route"));
|
||||
routePackage.route.serverRouteId = createdId;
|
||||
routePackage.route.submissionStatus = null;
|
||||
checkpointLocalUpload(routePackage);
|
||||
return { id: createdId, previousPois: [] };
|
||||
return { id: createdId, previousPois: [], recreated: replacedSubmission };
|
||||
}
|
||||
|
||||
async function syncLocalPoi(base, poi, index, routePackage) {
|
||||
@@ -1011,7 +1073,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
async function createServerRouteFromLocal(routePackage) {
|
||||
const { id, previousPois } = await prepareServerRoute(routePackage);
|
||||
const { id, previousPois, recreated } = await prepareServerRoute(routePackage);
|
||||
const base = `/routes/${encodeURIComponent(id)}/pois`;
|
||||
const orderedPois = [...(routePackage.pois || [])]
|
||||
.sort((left, right) => Number(left.sequence) - Number(right.sequence));
|
||||
@@ -1023,7 +1085,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
await syncLocalPictures(mediaBase, poi, oldPoi, routePackage);
|
||||
}
|
||||
await removeMissingServerItems(previousPois, orderedPois, 'serverPoiId', base);
|
||||
return id;
|
||||
return { id, recreated };
|
||||
}
|
||||
|
||||
async function uploadLocalRoute() {
|
||||
@@ -1039,7 +1101,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const routePackage = ns.LocalRoutes.get(localId);
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor.error.draft-missing"));
|
||||
|
||||
const serverRouteId = await createServerRouteFromLocal(routePackage);
|
||||
const { id: serverRouteId, recreated } = await createServerRouteFromLocal(routePackage);
|
||||
const uploadedAt = new Date().toISOString();
|
||||
const saved = ns.LocalRoutes.save({
|
||||
...routePackage,
|
||||
@@ -1053,7 +1115,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
state.pois = saved.pois;
|
||||
fillRouteForm(state.currentRoute);
|
||||
await loadRoutes();
|
||||
setStatus(ns.I18n.t("editor.submit-success"));
|
||||
setStatus(ns.I18n.t(recreated ? 'editor.resubmit-success' : 'editor.submit-success'));
|
||||
} finally {
|
||||
// Keep acknowledged server IDs even if a later media request failed.
|
||||
const latest = ns.LocalRoutes.get(routeId(state.currentRoute));
|
||||
|
||||
@@ -556,6 +556,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
createdAt: inputRoute.createdAt || now,
|
||||
updatedAt: now,
|
||||
serverRouteId: inputRoute.serverRouteId ?? null,
|
||||
submissionStatus: ['rejected', 'removed'].includes(inputRoute.submissionStatus) ? inputRoute.submissionStatus : null,
|
||||
uploadedAt: inputRoute.uploadedAt || null
|
||||
};
|
||||
const pois = (routePackage.pois || []).map((poi, index) => ({
|
||||
|
||||
@@ -135,6 +135,11 @@ globalThis.Wegwichtel ??= {};
|
||||
"rights-hint": "Wenn du Bilder oder Tonaufnahmen sendest, erlaubst du Wegwichtel, sie nach der Nutzungsvereinbarung zu verwenden. Sende nur Aufnahmen, für die du diese Erlaubnis geben darfst. Kinder brauchen die Zustimmung ihrer Sorgeberechtigten.",
|
||||
"rights-open": "Vereinbarung für Bilder und Ton lesen",
|
||||
"submit": "Zur Prüfung senden",
|
||||
"rejected": "Abgelehnt",
|
||||
"rejected-hint": "Diese Strecke wurde abgelehnt. Du kannst deinen Entwurf überarbeiten und erneut zur Prüfung senden.",
|
||||
"removed": "Entfernt",
|
||||
"removed-hint": "Diese Strecke ist nicht mehr online. Du kannst deinen Entwurf erneut zur Prüfung senden.",
|
||||
"resubmit-success": "Die Strecke wurde neu angelegt und zur Prüfung gesendet.",
|
||||
"submitted-hint": "Du kannst Änderungen erneut zur Prüfung senden, solange die Strecke noch nicht freigegeben wurde.",
|
||||
"draft-hint": "Dieser Entwurf bleibt auf deinem Gerät. Mit „Zur Prüfung senden“ reichst du ihn zur Freigabe ein.",
|
||||
"read-only-hint": "Diese Strecke ist für alle freigegeben und kann hier nicht mehr verändert werden.",
|
||||
|
||||
@@ -135,6 +135,11 @@ globalThis.Wegwichtel ??= {};
|
||||
"rights-hint": "By sending pictures or recordings, you allow Wegwichtel to use them under its terms of use. Only send recordings you are allowed to share this way. Children need their parent or guardian’s permission.",
|
||||
"rights-open": "Read the agreement for pictures and recordings",
|
||||
"submit": "Send for review",
|
||||
"rejected": "Not approved",
|
||||
"rejected-hint": "This trail was not approved. You can improve your draft and send it for review again.",
|
||||
"removed": "Removed",
|
||||
"removed-hint": "This trail is no longer online. You can send your draft for review again.",
|
||||
"resubmit-success": "A new trail was created and sent for review.",
|
||||
"submitted-hint": "You can send changes for review until the route has been approved.",
|
||||
"draft-hint": "This draft stays on your device. Choose “Send for review” to submit it for approval.",
|
||||
"read-only-hint": "This trail is available to everyone and can no longer be changed here.",
|
||||
|
||||
Reference in New Issue
Block a user