mirror of
https://github.com/simonw/datasette.git
synced 2026-09-18 22:44:07 +02:00
Refactor the create table dialog to use the shared modal, refs #2790
This commit is contained in:
parent
de37f1451f
commit
c82a98c88a
3 changed files with 51 additions and 138 deletions
|
|
@ -2077,46 +2077,8 @@ datasette-autocomplete input[type="text"],
|
|||
}
|
||||
|
||||
dialog.table-create-dialog {
|
||||
--ink: #0f0f0f;
|
||||
--paper: #eef6ff;
|
||||
--muted: #6b6b6b;
|
||||
--rule: #d8e6f5;
|
||||
--accent: #1a56db;
|
||||
--card: #ffffff;
|
||||
border: none;
|
||||
border-radius: var(--modal-border-radius, 0.75rem);
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
width: min(980px, calc(100vw - 32px));
|
||||
max-width: 95vw;
|
||||
max-height: min(780px, calc(100vh - 32px));
|
||||
box-shadow: var(--modal-shadow, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04));
|
||||
animation: datasette-modal-slide-in var(--modal-animation-duration, 0.2s) ease-out;
|
||||
overflow: hidden;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
dialog.table-create-dialog[open] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
dialog.table-create-dialog::backdrop {
|
||||
background: var(--modal-backdrop-bg, rgba(0, 0, 0, 0.5));
|
||||
backdrop-filter: var(--modal-backdrop-blur, blur(4px));
|
||||
-webkit-backdrop-filter: var(--modal-backdrop-blur, blur(4px));
|
||||
animation: datasette-modal-fade-in var(--modal-animation-duration, 0.2s) ease-out;
|
||||
}
|
||||
|
||||
.table-create-dialog .modal-header {
|
||||
padding: 20px 24px 12px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.table-create-dialog .modal-title {
|
||||
|
|
@ -2124,9 +2086,6 @@ dialog.table-create-dialog::backdrop {
|
|||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.table-create-form {
|
||||
|
|
@ -2565,17 +2524,6 @@ select.table-create-input {
|
|||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.table-create-dialog .modal-footer {
|
||||
padding: 14px 20px;
|
||||
border-top: 1px solid var(--rule);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
background: var(--paper);
|
||||
}
|
||||
|
||||
.table-create-mode-link {
|
||||
color: var(--accent);
|
||||
font-size: 0.9rem;
|
||||
|
|
@ -2586,39 +2534,7 @@ select.table-create-input {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.table-create-dialog .btn {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 9px 20px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
font-family: inherit;
|
||||
transition: background 0.12s;
|
||||
}
|
||||
|
||||
.table-create-dialog .btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.table-create-dialog .btn-ghost:hover {
|
||||
background: var(--rule);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.table-create-dialog .btn-primary {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.table-create-dialog .btn-primary:hover {
|
||||
background: #1949b8;
|
||||
}
|
||||
|
||||
.table-create-dialog .btn:disabled,
|
||||
.table-create-dialog .modal-btn:disabled,
|
||||
.table-create-add-column:disabled,
|
||||
.table-create-icon-button:disabled {
|
||||
opacity: 0.55;
|
||||
|
|
|
|||
|
|
@ -915,6 +915,7 @@ function showTableCreateDialogError(state, message) {
|
|||
|
||||
function setTableCreateDialogSaving(state, isSaving) {
|
||||
state.isSaving = isSaving;
|
||||
state.modal.busy = isSaving;
|
||||
state.columnList
|
||||
.querySelectorAll("input, select, button")
|
||||
.forEach(function (control) {
|
||||
|
|
@ -2043,8 +2044,7 @@ async function createTableFromDataPreview(state) {
|
|||
var tableUrl =
|
||||
responseData.table_url ||
|
||||
fallbackTableUrl(responseData.table || payload.table);
|
||||
state.shouldRestoreFocus = false;
|
||||
state.dialog.close();
|
||||
state.modal.close({ restoreFocus: false });
|
||||
if (tableUrl) {
|
||||
location.href = tableUrl;
|
||||
} else {
|
||||
|
|
@ -2118,8 +2118,7 @@ async function saveTableCreateDialog(state) {
|
|||
var tableUrl =
|
||||
responseData.table_url ||
|
||||
fallbackTableUrl(responseData.table || payload.table);
|
||||
state.shouldRestoreFocus = false;
|
||||
state.dialog.close();
|
||||
state.modal.close({ restoreFocus: false });
|
||||
if (tableUrl) {
|
||||
location.href = tableUrl;
|
||||
} else {
|
||||
|
|
@ -2141,18 +2140,6 @@ function confirmDiscardTableCreateChanges(state) {
|
|||
return window.confirm("Discard this new table?");
|
||||
}
|
||||
|
||||
function closeTableCreateDialogIfConfirmed(state) {
|
||||
if (!state || state.isSaving) {
|
||||
return false;
|
||||
}
|
||||
if (!confirmDiscardTableCreateChanges(state)) {
|
||||
return false;
|
||||
}
|
||||
state.shouldRestoreFocus = true;
|
||||
state.dialog.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
function ensureTableCreateDialog(manager) {
|
||||
if (tableCreateDialogState) {
|
||||
return tableCreateDialogState;
|
||||
|
|
@ -2161,7 +2148,8 @@ function ensureTableCreateDialog(manager) {
|
|||
return null;
|
||||
}
|
||||
|
||||
var dialog = document.createElement("dialog");
|
||||
var modal = DatasetteModal.create();
|
||||
var dialog = modal.dialog;
|
||||
dialog.id = TABLE_CREATE_DIALOG_ID;
|
||||
dialog.className = "table-create-dialog";
|
||||
dialog.setAttribute("aria-labelledby", "table-create-title");
|
||||
|
|
@ -2198,14 +2186,15 @@ function ensureTableCreateDialog(manager) {
|
|||
<div class="modal-footer">
|
||||
<a href="#" class="table-create-mode-link table-create-from-data">Create table from data</a>
|
||||
<a href="#" class="table-create-mode-link table-create-manual" hidden>Create table manually</a>
|
||||
<button type="button" class="btn btn-ghost table-create-cancel">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary table-create-save">Create table</button>
|
||||
<button type="button" class="modal-btn modal-btn-ghost table-create-cancel">Cancel</button>
|
||||
<button type="submit" class="modal-btn modal-btn-primary table-create-save">Create table</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
document.body.appendChild(dialog);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
tableCreateDialogState = {
|
||||
modal: modal,
|
||||
dialog: dialog,
|
||||
form: dialog.querySelector(".table-create-form"),
|
||||
title: dialog.querySelector(".modal-title"),
|
||||
|
|
@ -2225,8 +2214,6 @@ function ensureTableCreateDialog(manager) {
|
|||
manualCreateLink: dialog.querySelector(".table-create-manual"),
|
||||
cancelButton: dialog.querySelector(".table-create-cancel"),
|
||||
saveButton: dialog.querySelector(".table-create-save"),
|
||||
currentButton: null,
|
||||
shouldRestoreFocus: true,
|
||||
isSaving: false,
|
||||
mode: "manual",
|
||||
dataPreviewRows: null,
|
||||
|
|
@ -2266,7 +2253,7 @@ function ensureTableCreateDialog(manager) {
|
|||
tableCreateDialogState.dataTextarea.focus();
|
||||
return;
|
||||
}
|
||||
closeTableCreateDialogIfConfirmed(tableCreateDialogState);
|
||||
modal.requestClose("cancel");
|
||||
});
|
||||
|
||||
tableCreateDialogState.createFromDataLink.addEventListener(
|
||||
|
|
@ -2364,36 +2351,14 @@ function ensureTableCreateDialog(manager) {
|
|||
updateTableCreateDialogButtons(tableCreateDialogState);
|
||||
});
|
||||
|
||||
dialog.addEventListener("click", function (ev) {
|
||||
if (ev.target === dialog) {
|
||||
closeTableCreateDialogIfConfirmed(tableCreateDialogState);
|
||||
}
|
||||
});
|
||||
|
||||
dialog.addEventListener("keydown", function (ev) {
|
||||
if (ev.key !== "Escape") {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
closeTableCreateDialogIfConfirmed(tableCreateDialogState);
|
||||
});
|
||||
|
||||
dialog.addEventListener("cancel", function (ev) {
|
||||
ev.preventDefault();
|
||||
closeTableCreateDialogIfConfirmed(tableCreateDialogState);
|
||||
});
|
||||
modal.beforeClose = function (reason) {
|
||||
return confirmDiscardTableCreateChanges(tableCreateDialogState);
|
||||
};
|
||||
|
||||
dialog.addEventListener("close", function () {
|
||||
var state = tableCreateDialogState;
|
||||
clearTableCreateDialogError(state);
|
||||
setTableCreateDialogSaving(state, false);
|
||||
if (
|
||||
state.shouldRestoreFocus &&
|
||||
state.currentButton &&
|
||||
document.contains(state.currentButton)
|
||||
) {
|
||||
state.currentButton.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return tableCreateDialogState;
|
||||
|
|
@ -2414,15 +2379,12 @@ function openTableCreateDialog(button, manager) {
|
|||
menu.open = false;
|
||||
}
|
||||
state.manager = manager;
|
||||
state.currentButton = button;
|
||||
state.shouldRestoreFocus = true;
|
||||
|
||||
state.title.textContent = "Create a table in " + data.databaseName;
|
||||
clearTableCreateDialogError(state);
|
||||
resetTableCreateDialog(state);
|
||||
loadTableCreateForeignKeyTargets(state);
|
||||
if (!state.dialog.open) {
|
||||
state.dialog.showModal();
|
||||
}
|
||||
state.modal.show({ trigger: button });
|
||||
state.tableName.focus();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1832,3 +1832,38 @@ def test_modal_disconnect_cleans_up_pending_escape(page, datasette_server):
|
|||
page.keyboard.press("Escape")
|
||||
page.wait_for_function("closeAttempts === 1")
|
||||
expect(dialog).to_be_visible()
|
||||
|
||||
|
||||
@pytest.mark.playwright
|
||||
@pytest.mark.parametrize("kind", ["create"])
|
||||
def test_schema_modal_escape_confirmation_and_focus(page, datasette_server, kind):
|
||||
from playwright.sync_api import expect
|
||||
|
||||
path = "data" if kind == "create" else "data/projects"
|
||||
page.goto(datasette_server + path)
|
||||
menu = page.locator("details.actions-menu-links")
|
||||
menu.locator("summary").click()
|
||||
selector = "data-database-action" if kind == "create" else "data-table-action"
|
||||
menu.locator(f'button[{selector}="{kind}-table"]').click()
|
||||
dialog = page.locator(f"#table-{kind}-dialog")
|
||||
if kind == "create":
|
||||
dialog.locator('input[name="table"]').fill("unsaved_table")
|
||||
else:
|
||||
dialog.locator(".table-alter-add-column").click()
|
||||
# Real browser confirms, including WebKit, should appear once and stay usable.
|
||||
confirmations = []
|
||||
|
||||
def reject(prompt):
|
||||
confirmations.append(prompt.message)
|
||||
prompt.dismiss()
|
||||
|
||||
page.on("dialog", reject)
|
||||
with page.expect_event("dialog"):
|
||||
page.keyboard.press("Escape")
|
||||
expect(dialog).to_be_visible()
|
||||
assert len(confirmations) == 1
|
||||
page.remove_listener("dialog", reject)
|
||||
page.on("dialog", lambda prompt: prompt.accept())
|
||||
page.keyboard.press("Escape")
|
||||
expect(dialog).not_to_be_visible()
|
||||
expect(menu.locator("summary")).to_be_focused()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue