new form page row + import page dialog fix
This commit is contained in:
@@ -3,31 +3,48 @@
|
||||
|
||||
The card carries its own poll trigger, the way admin/ecb's status card does:
|
||||
while the import is running each answer is another card with the trigger
|
||||
still on it, and the answer that ends the import is the success or failure
|
||||
alert instead — which has no trigger, and that is what stops the polling.
|
||||
still on it, and the answer that ends the import is the success dialog or
|
||||
failure alert instead — which has no trigger, and that is what stops the
|
||||
polling.
|
||||
|
||||
It is also a blocking dialog rather than an inline card: an import that is
|
||||
actually done answers a stale-looking page if the tab is left alone, and a
|
||||
second click on Import is how a duplicate row gets written. Pinning the
|
||||
modal over the page for as long as the job runs makes "click it again" not
|
||||
an option, and there is deliberately no way to dismiss it early — Escape and
|
||||
a backdrop click do nothing here, unlike the dialogs in ui/dialog.html.
|
||||
#}
|
||||
{#
|
||||
The card sits inside #submission-status, which is a polite live region: left
|
||||
alone, a screen reader would read the whole card out again every second. It
|
||||
silences itself instead, and the alert that replaces it when the import ends
|
||||
— which is the part worth hearing — is announced by the region as usual.
|
||||
#}
|
||||
<div id="import-progress" class="import-progress" aria-live="off"
|
||||
<div id="import-progress"
|
||||
hx-get="{{ poll_url }}" hx-trigger="every 1s"
|
||||
hx-target="#import-progress" hx-swap="outerHTML">
|
||||
<h3>{{ heading }}</h3>
|
||||
<div class="progress-line">
|
||||
<progress max="100" value="{{ percent }}" aria-label="{{ label }}"></progress>
|
||||
<span class="progress-percent">{{ percent }}%</span>
|
||||
</div>
|
||||
<p class="progress-rows"><strong>{{ rows }}</strong></p>
|
||||
<dl class="progress-stats">
|
||||
{% for stat in stats %}
|
||||
<div>
|
||||
<dt>{{ stat.label }}</dt>
|
||||
<dd>{{ stat.value }}</dd>
|
||||
hx-target="#import-progress" hx-swap="outerHTML"
|
||||
x-data="{ importProgressOpen: true }">
|
||||
<div x-cloak x-show="importProgressOpen" x-trap.inert.noscroll="importProgressOpen"
|
||||
class="fixed inset-0 z-30 flex items-end justify-center bg-black/20 p-4 pb-8 backdrop-blur-md sm:items-center lg:p-8"
|
||||
role="dialog" aria-modal="true" aria-labelledby="import-progress-heading">
|
||||
<div class="flex w-full max-w-lg flex-col overflow-hidden rounded-radius border border-outline bg-surface text-on-surface dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark">
|
||||
{#
|
||||
The card sits inside a polite live region: left alone, a screen reader
|
||||
would read the whole card out again every second. It silences itself
|
||||
instead, and the dialog or alert that replaces it when the import ends
|
||||
— which is the part worth hearing — is announced as usual.
|
||||
#}
|
||||
<div class="import-progress" aria-live="off">
|
||||
<h3 id="import-progress-heading">{{ heading }}</h3>
|
||||
<div class="progress-line">
|
||||
<progress max="100" value="{{ percent }}" aria-label="{{ label }}"></progress>
|
||||
<span class="progress-percent">{{ percent }}%</span>
|
||||
</div>
|
||||
<p class="progress-rows"><strong>{{ rows }}</strong></p>
|
||||
<dl class="progress-stats">
|
||||
{% for stat in stats %}
|
||||
<div>
|
||||
<dt>{{ stat.label }}</dt>
|
||||
<dd>{{ stat.value }}</dd>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
<p class="hint">{{ hint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
<p class="hint">{{ hint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
10
web/templates/pages/import_export/import/success_dialog.html
Normal file
10
web/templates/pages/import_export/import/success_dialog.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{#
|
||||
The import's success confirmation — crate::pages::import_export::import::ui::render_success.
|
||||
|
||||
A blocking dialog rather than the toast every other successful form uses:
|
||||
the user has been staring at the progress dialog this replaces, and a toast
|
||||
that shows itself and times out on its own schedule is exactly the "did it
|
||||
actually finish?" ambiguity that motivated the dialog in the first place.
|
||||
#}
|
||||
{% import "ui/dialog.html" as dialog %}
|
||||
{% call dialog::success(locale, title, message) %}{% endcall %}
|
||||
@@ -50,3 +50,49 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{#
|
||||
Blocking confirmation modal — Penguin UI's success modal, taken from
|
||||
penguinui-components/modal/modal-alerts.html.
|
||||
|
||||
Used where a toast's own timer would leave the "did it actually finish?"
|
||||
question open — a long-running import in particular, where the user has been
|
||||
staring at a progress dialog and needs the same dialog to tell them it is
|
||||
done, not a notification that can appear and vanish while they are looking
|
||||
at the wrong corner of the screen. Dismissed the same three ways as
|
||||
dialog::error: the close button, Escape, or a click on the backdrop.
|
||||
|
||||
{% import "ui/dialog.html" as dialog %}
|
||||
{% call dialog::success(locale, "Import complete", message) %}{% endcall %}
|
||||
#}
|
||||
{% macro success(locale, title, message) %}
|
||||
<div x-data="{ successModalIsOpen: true }">
|
||||
<div x-cloak x-show="successModalIsOpen" x-transition.opacity.duration.200ms x-trap.inert.noscroll="successModalIsOpen" x-on:keydown.esc.window="successModalIsOpen = false" x-on:click.self="successModalIsOpen = false" class="fixed inset-0 z-30 flex items-end justify-center bg-black/20 p-4 pb-8 backdrop-blur-md sm:items-center lg:p-8" role="dialog" aria-modal="true" aria-labelledby="successModalTitle">
|
||||
<!-- Modal Dialog -->
|
||||
<div x-show="successModalIsOpen" x-transition:enter="transition ease-out duration-200 delay-100 motion-reduce:transition-opacity" x-transition:enter-start="opacity-0 scale-50" x-transition:enter-end="opacity-100 scale-100" class="flex max-w-lg flex-col gap-4 overflow-hidden rounded-radius border border-outline bg-surface text-on-surface dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark">
|
||||
<!-- Dialog Header -->
|
||||
<div class="flex items-center justify-between border-b border-outline bg-surface-alt/60 px-4 py-2 dark:border-outline-dark dark:bg-surface-dark/20">
|
||||
<div class="flex items-center justify-center rounded-full bg-success/20 text-success p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-6" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<button x-on:click="successModalIsOpen = false" aria-label="{{ locale.lookup("ui-close-dialog") }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor" fill="none" stroke-width="1.4" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Dialog Body -->
|
||||
<div class="px-4 text-center">
|
||||
<h3 id="successModalTitle" class="mb-2 font-semibold tracking-wide text-on-surface-strong dark:text-on-surface-dark-strong">{{ title }}</h3>
|
||||
<p class="whitespace-pre-wrap">{{ message }}</p>
|
||||
</div>
|
||||
<!-- Dialog Footer -->
|
||||
<div class="flex items-center justify-center border-outline p-4 dark:border-outline-dark">
|
||||
<button x-on:click="successModalIsOpen = false" type="button" class="w-full whitespace-nowrap rounded-radius border border-success bg-success px-4 py-2 text-center text-sm font-semibold tracking-wide text-on-success transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success active:opacity-100 active:outline-offset-0">{{ locale.lookup("import-progress-done-close") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
Reference in New Issue
Block a user