Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@
6
],
"block-spacing": "error",
"array-bracket-spacing": [
"error",
"always"
],
"multiline-comment-style": "error",
"computed-property-spacing": "off",
"space-in-parens": "off",
Expand Down
61 changes: 61 additions & 0 deletions public/assets/sandbox-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,63 @@ function sameAction(left, right) {
&& left.source === right.source;
}

/** Captures user-editable controls before dashboard markup is replaced. */
function snapshotForm(form) {
const occurrences = new Map();
return Array.from(form.elements).flatMap((control) => {
if (!control.name || ['hidden', 'submit', 'button', 'reset', 'file'].includes(control.type)) {
return [];
}

const occurrence = occurrences.get(control.name) || 0;
occurrences.set(control.name, occurrence + 1);
return [{
name: control.name,
occurrence,
value: control.value,
checked: control.checked,
selected: control.options ? Array.from(control.options).filter((option) => option.selected).map((option) => option.value) : null,
}];
});
}

/** Finds the regenerated form without relying on mutable field values. */
function actionForm(context) {
const candidates = Array.from(dashboard.querySelectorAll('form[data-ajax]')).filter((form) => {
const candidate = actionContext(form);
return candidate.action === context.action
&& candidate.board === context.board
&& candidate.section === context.section;
});

return candidates.find((form) => sameAction(actionContext(form), context)) || (candidates.length === 1 ? candidates[0] : null);
}

/** Restores submitted values after an error response regenerates the dashboard. */
function restoreForm(context, snapshot) {
const form = actionForm(context);
if (!form) {
return;
}

for (const saved of snapshot) {
const controls = Array.from(form.elements).filter((control) => control.name === saved.name);
const control = controls[saved.occurrence];
if (!control) {
continue;
}
if (saved.selected !== null) {
for (const option of control.options) {
option.selected = saved.selected.includes(option.value);
}
} else if (control.type === 'checkbox' || control.type === 'radio') {
control.checked = saved.checked;
} else {
control.value = saved.value;
}
}
}

/** Reapplies pending state after AJAX replaces dashboard markup. */
function syncPendingActions() {
pendingActions.forEach((pending) => {
Expand Down Expand Up @@ -136,6 +193,7 @@ function bindAjax() {
}

const context = actionContext(form);
const formSnapshot = snapshotForm(form);
const submitter = event.submitter;
const original = submitter ? submitter.innerHTML : '';
const actionId = nextActionId++;
Expand Down Expand Up @@ -170,6 +228,9 @@ function bindAjax() {
if (typeof data.html === 'string' && data.html !== '') {
dashboard.innerHTML = data.html;
bindAjax();
if (data.error) {
restoreForm(context, formSnapshot);
}
syncPendingActions();
}
showActionResult(data, context);
Expand Down
10 changes: 5 additions & 5 deletions src/QuickInstall/Sandbox/Web/templates/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<div class="section-head">
<div>
<h2><svg class="icon" aria-hidden="true"><use href="#icon-boards"></use></svg>Boards</h2>
<p>Start, stop, seed, and inspect local Docker-backed phpBB installs.</p>
<p>Manage your local phpBB boards and add sample content.</p>
</div>
</div>
<?php if (!$boards): ?>
Expand Down Expand Up @@ -159,7 +159,7 @@
<div class="section-head">
<div>
<h2><svg class="icon" aria-hidden="true"><use href="#icon-create"></use></svg>Create board</h2>
<p>Choose the phpBB source, runtime, port, and optional seed preset.</p>
<p>Set up a new phpBB board for development or testing.</p>
</div>
</div>
<form method="post" class="card settings-form" data-ajax>
Expand All @@ -181,7 +181,7 @@
<div class="section-head">
<div>
<h2><svg class="icon" aria-hidden="true"><use href="#icon-customisations"></use></svg>Customisations</h2>
<p>Bind or copy local extension and style work into a board.</p>
<p>Add extensions and styles from your local projects.</p>
</div>
</div>
<div class="split">
Expand Down Expand Up @@ -209,7 +209,7 @@
<div class="section-head">
<div>
<h2><svg class="icon" aria-hidden="true"><use href="#icon-sources"></use></svg>Sources</h2>
<p>Fetch and inspect phpBB sources used by boards.</p>
<p>Manage the phpBB versions available for your boards.</p>
</div>
</div>
<form method="post" class="card settings-form compact-form joined-form" data-ajax>
Expand Down Expand Up @@ -264,7 +264,7 @@
<div class="section-head">
<div>
<h2><svg class="icon" aria-hidden="true"><use href="#icon-activity"></use></svg>Activity log</h2>
<p>Queued and completed actions from this browser session.</p>
<p>Review actions and results from this session.</p>
</div>
<button type="button" class="secondary" data-clear-log>Clear</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/QuickInstall/Sandbox/Web/templates/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<header class="topbar">
<div>
<h1>QuickInstall Dashboard</h1>
<p class="lede">Manage disposable phpBB boards backed by the same Docker services as the CLI.</p>
<p class="lede">Create and manage local phpBB boards for development and testing.</p>
</div>
<div class="actions">
<form method="post" data-ajax>
Expand Down
4 changes: 4 additions & 0 deletions tests/Integration/WebApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ public function testDashboardJavascriptTracksConcurrentActions(): void
self::assertStringContainsString('entry.status = runningAssigned ? \'queued\' : \'running\';', $javascript);
self::assertStringContainsString("document.querySelectorAll('[data-mounted-toggle]')", $javascript);
self::assertStringContainsString("button.textContent = expanded ? button.dataset.moreLabel : 'Show less';", $javascript);
self::assertStringContainsString('const formSnapshot = snapshotForm(form);', $javascript);
self::assertStringContainsString('if (data.error) {', $javascript);
self::assertStringContainsString('restoreForm(context, formSnapshot);', $javascript);
self::assertStringContainsString("control.type === 'checkbox' || control.type === 'radio'", $javascript);
}

public function testNarrowBoardCardsStackMountedLists(): void
Expand Down