add column and alias are separate
This commit is contained in:
@@ -133,7 +133,7 @@ admin-no-profile-tables = Tento profil nemá vlastní tabulky.
|
||||
admin-no-dependencies = Bez závislostí
|
||||
admin-depends-on-prefix = Závisí na
|
||||
admin-row-display-prefix = zobrazení:
|
||||
admin-add-rename-columns = Přidat / přejmenovat sloupce
|
||||
admin-column-presentation = Aliasy sloupců
|
||||
admin-delete-table = Smazat tabulku
|
||||
admin-who-may-use-it = Kdo ji může používat
|
||||
admin-pick-table-hint = Vyberte tabulku, abyste do ní přidali sloupce, smazali ji nebo určili, kdo ji může používat.
|
||||
@@ -156,16 +156,17 @@ admin-select-table-inspect = Vyberte tabulku, abyste zobrazili její sloupce.
|
||||
admin-no-visible-columns = Tato tabulka nemá žádné viditelné sloupce.
|
||||
admin-system-columns-label = Systémové sloupce · skryté před uživateli
|
||||
|
||||
# --- Stránky definice tabulky (sloupce / smazat / kopírovat / šablona / historie)
|
||||
td-columns-title = Sloupce
|
||||
# --- Stránky definice tabulky (přidat sloupce / zobrazení / smazat / kopírovat / šablona / historie)
|
||||
td-add-columns-title = Přidat sloupce
|
||||
td-presentation-title = Zobrazení sloupců
|
||||
td-add-columns-to = Přidat sloupce do
|
||||
td-append-hint = Sloupce se přidávají na konec. Nic, co už existuje, se nemění a nové sloupce lze indexovat hned při přidání.
|
||||
td-rename-column = Přejmenovat sloupec
|
||||
td-rename-column = Aliasy a pořadí sloupců
|
||||
td-rename-hint = Přejmenovává se jen zobrazený název, ne fyzický sloupec pod ním, takže uložená data a skripty zůstávají nedotčené. Možné jen dokud tabulka nemá žádné řádky.
|
||||
td-column-label = Sloupec
|
||||
td-choose-column = Vyberte sloupec
|
||||
td-new-name = Nový název
|
||||
td-rename-button = Přejmenovat sloupec
|
||||
td-rename-button = Uložit zobrazení
|
||||
td-columns-now = Současné sloupce
|
||||
td-col-column = Sloupec
|
||||
td-col-type = Typ
|
||||
@@ -234,8 +235,10 @@ td-depends-on-prefix = závisí na
|
||||
td-acts-on-profile = Vše na této stránce se týká tohoto profilu.
|
||||
td-global-label = Globální — všechny profily
|
||||
td-tab-aria = akce s tabulkou
|
||||
td-tab-columns = Sloupce
|
||||
td-tab-columns-sub = Přidat sloupce a přejmenovat je
|
||||
td-tab-add-columns = Přidat sloupce
|
||||
td-tab-add-columns-sub = Připojit nové sloupce
|
||||
td-tab-presentation = Zobrazení
|
||||
td-tab-presentation-sub = Změnit aliasy a pořadí
|
||||
td-tab-delete = Smazat
|
||||
td-tab-delete-sub = Odebrat tuto tabulku
|
||||
td-tab-access = Přístup
|
||||
|
||||
@@ -137,7 +137,7 @@ admin-no-profile-tables = This profile has no tables of its own.
|
||||
admin-no-dependencies = No dependencies
|
||||
admin-depends-on-prefix = Depends on
|
||||
admin-row-display-prefix = display:
|
||||
admin-add-rename-columns = Add / rename columns
|
||||
admin-column-presentation = Column aliases
|
||||
admin-delete-table = Delete table
|
||||
admin-who-may-use-it = Who may use it
|
||||
admin-pick-table-hint = Pick a table to add columns to it, delete it, or say who may use it.
|
||||
@@ -158,16 +158,17 @@ admin-select-table-inspect = Select a table to inspect its columns.
|
||||
admin-no-visible-columns = This table has no visible columns.
|
||||
admin-system-columns-label = System columns · hidden from users
|
||||
|
||||
# --- Table definition pages (columns / delete / copy / template / history) -
|
||||
td-columns-title = Columns
|
||||
# --- Table definition pages (add columns / presentation / delete / copy / template / history) -
|
||||
td-add-columns-title = Add columns
|
||||
td-presentation-title = Column presentation
|
||||
td-add-columns-to = Add columns to
|
||||
td-append-hint = Columns are appended. Nothing that already exists is changed, and the new columns can be indexed as they are added.
|
||||
td-rename-column = Rename a column
|
||||
td-rename-column = Column aliases and order
|
||||
td-rename-hint = Renames what the column is called, not the physical column underneath, so stored data and scripts are untouched. Only possible while the table has no rows.
|
||||
td-column-label = Column
|
||||
td-choose-column = Choose a column
|
||||
td-new-name = New name
|
||||
td-rename-button = Rename column
|
||||
td-rename-button = Save presentation
|
||||
td-columns-now = Columns it has now
|
||||
td-col-column = Column
|
||||
td-col-type = Type
|
||||
@@ -234,8 +235,10 @@ td-depends-on-prefix = depends on
|
||||
td-acts-on-profile = Everything on this page acts on this profile.
|
||||
td-global-label = Global — all profiles
|
||||
td-tab-aria = table actions
|
||||
td-tab-columns = Columns
|
||||
td-tab-columns-sub = Append columns and rename them
|
||||
td-tab-add-columns = Add columns
|
||||
td-tab-add-columns-sub = Append new columns
|
||||
td-tab-presentation = Presentation
|
||||
td-tab-presentation-sub = Change aliases and order
|
||||
td-tab-delete = Delete
|
||||
td-tab-delete-sub = Drop this table
|
||||
td-tab-access = Access
|
||||
|
||||
@@ -133,7 +133,7 @@ admin-no-profile-tables = Tento profil nemá vlastné tabuľky.
|
||||
admin-no-dependencies = Bez závislostí
|
||||
admin-depends-on-prefix = Závisí od
|
||||
admin-row-display-prefix = zobrazenie:
|
||||
admin-add-rename-columns = Pridať / premenovať stĺpce
|
||||
admin-column-presentation = Aliasy stĺpcov
|
||||
admin-delete-table = Vymazať tabuľku
|
||||
admin-who-may-use-it = Kto ju môže používať
|
||||
admin-pick-table-hint = Vyberte tabuľku, aby ste do nej pridali stĺpce, vymazali ju alebo určili, kto ju môže používať.
|
||||
@@ -156,16 +156,17 @@ admin-select-table-inspect = Vyberte tabuľku, aby ste zobrazili jej stĺpce.
|
||||
admin-no-visible-columns = Táto tabuľka nemá žiadne viditeľné stĺpce.
|
||||
admin-system-columns-label = Systémové stĺpce · skryté pred používateľmi
|
||||
|
||||
# --- Stránky definície tabuľky (stĺpce / vymazať / kopírovať / šablóna / história)
|
||||
td-columns-title = Stĺpce
|
||||
# --- Stránky definície tabuľky (pridať stĺpce / zobrazenie / vymazať / kopírovať / šablóna / história)
|
||||
td-add-columns-title = Pridať stĺpce
|
||||
td-presentation-title = Zobrazenie stĺpcov
|
||||
td-add-columns-to = Pridať stĺpce do
|
||||
td-append-hint = Stĺpce sa pridávajú na koniec. Nič, čo už existuje, sa nemení a nové stĺpce možno indexovať hneď pri pridaní.
|
||||
td-rename-column = Premenovať stĺpec
|
||||
td-rename-column = Aliasy a poradie stĺpcov
|
||||
td-rename-hint = Premenúva sa len zobrazený názov, nie fyzický stĺpec pod ním, takže uložené údaje a skripty zostávajú nedotknuté. Možné len kým tabuľka nemá žiadne riadky.
|
||||
td-column-label = Stĺpec
|
||||
td-choose-column = Vyberte stĺpec
|
||||
td-new-name = Nový názov
|
||||
td-rename-button = Premenovať stĺpec
|
||||
td-rename-button = Uložiť zobrazenie
|
||||
td-columns-now = Súčasné stĺpce
|
||||
td-col-column = Stĺpec
|
||||
td-col-type = Typ
|
||||
@@ -234,8 +235,10 @@ td-depends-on-prefix = závisí od
|
||||
td-acts-on-profile = Všetko na tejto stránke sa týka tohto profilu.
|
||||
td-global-label = Globálne — všetky profily
|
||||
td-tab-aria = akcie s tabuľkou
|
||||
td-tab-columns = Stĺpce
|
||||
td-tab-columns-sub = Pridať stĺpce a premenovať ich
|
||||
td-tab-add-columns = Pridať stĺpce
|
||||
td-tab-add-columns-sub = Pripojiť nové stĺpce
|
||||
td-tab-presentation = Zobrazenie
|
||||
td-tab-presentation-sub = Zmeniť aliasy a poradie
|
||||
td-tab-delete = Vymazať
|
||||
td-tab-delete-sub = Odstrániť túto tabuľku
|
||||
td-tab-access = Prístup
|
||||
|
||||
@@ -301,7 +301,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn the_table_definition_pages_are_mounted_and_need_a_session() {
|
||||
for path in [
|
||||
"/admin/tables/columns",
|
||||
"/admin/tables/columns/add",
|
||||
"/admin/tables/presentation",
|
||||
"/admin/tables/delete",
|
||||
"/admin/profiles/copy",
|
||||
"/admin/profiles/history",
|
||||
@@ -321,8 +322,8 @@ mod tests {
|
||||
assert_eq!(status, axum::http::StatusCode::PERMANENT_REDIRECT);
|
||||
|
||||
for path in [
|
||||
"/admin/tables/columns",
|
||||
"/admin/tables/columns/builder",
|
||||
"/admin/tables/columns/add",
|
||||
"/admin/tables/columns/add/builder",
|
||||
"/admin/tables/presentation",
|
||||
"/admin/tables/delete",
|
||||
"/admin/profiles/copy",
|
||||
@@ -452,7 +453,7 @@ mod tests {
|
||||
("/admin/tables/delete", "profile=billing&table=invoice"),
|
||||
("/admin/profiles/copy", "profile=billing&table=invoice"),
|
||||
("/admin/tables/presentation", "profile=billing&table=invoice"),
|
||||
("/admin/tables/columns", "profile=billing&table=invoice"),
|
||||
("/admin/tables/columns/add", "profile=billing&table=invoice"),
|
||||
("/admin/tables/builder", ""),
|
||||
("/admin/tables", ""),
|
||||
("/admin/logic", ""),
|
||||
|
||||
@@ -156,7 +156,7 @@ mod tests {
|
||||
|
||||
let html = render_workspace(&page);
|
||||
for route in [
|
||||
"/admin/tables/columns?profile=books&table=invoice",
|
||||
"/admin/tables/presentation?profile=books&table=invoice",
|
||||
"/admin/tables/delete?profile=books&table=invoice",
|
||||
"/admin/tables/new?profile=books",
|
||||
"/admin/profiles/copy?profile=books",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Reads the context every one of the five pages shows.
|
||||
//! Reads the context every table-definition page shows.
|
||||
//!
|
||||
//! One loader serves them all, because they all need the same thing: which
|
||||
//! table is being worked on, and what it currently is. Four calls, in this
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The request handlers behind the five table-definition pages.
|
||||
//! The request handlers behind the six table-definition pages.
|
||||
//!
|
||||
//! Each page has a GET that renders it and, except for the read-only history,
|
||||
//! a POST per write it offers. A write answers with its own page's body,
|
||||
@@ -43,7 +43,8 @@ use super::{
|
||||
/// which fragment a write on it swaps back.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Page {
|
||||
Columns,
|
||||
AddColumns,
|
||||
Presentation,
|
||||
Delete,
|
||||
Copy,
|
||||
Template,
|
||||
@@ -53,7 +54,8 @@ enum Page {
|
||||
impl Page {
|
||||
fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Columns => "columns",
|
||||
Self::AddColumns => "add-columns",
|
||||
Self::Presentation => "presentation",
|
||||
Self::Delete => "delete",
|
||||
Self::Copy => "copy",
|
||||
Self::Template => "template",
|
||||
@@ -63,7 +65,8 @@ impl Page {
|
||||
|
||||
fn render_page(self, page: &TableDefinitionPageState) -> String {
|
||||
match self {
|
||||
Self::Columns => ui::render_columns_page(page),
|
||||
Self::AddColumns => ui::render_add_columns_page(page),
|
||||
Self::Presentation => ui::render_presentation_page(page),
|
||||
Self::Delete => ui::render_delete_page(page),
|
||||
Self::Copy => ui::render_copy_page(page),
|
||||
Self::Template => ui::render_template_page(page),
|
||||
@@ -74,7 +77,8 @@ impl Page {
|
||||
/// The `#table-panel` swap. History has no writes, so it never asks.
|
||||
fn render_fragment(self, page: &TableDefinitionPageState) -> String {
|
||||
match self {
|
||||
Self::Columns => ui::render_columns_fragment(page),
|
||||
Self::AddColumns => ui::render_add_columns_fragment(page),
|
||||
Self::Presentation => ui::render_presentation_fragment(page),
|
||||
Self::Delete => ui::render_delete_fragment(page),
|
||||
Self::Copy => ui::render_copy_fragment(page),
|
||||
Self::Template => ui::render_template_fragment(page),
|
||||
@@ -85,13 +89,22 @@ impl Page {
|
||||
|
||||
// ── The pages ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// GET /admin/tables/columns
|
||||
pub(crate) async fn columns_page(
|
||||
/// GET /admin/tables/columns/add
|
||||
pub(crate) async fn add_columns_page(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Query(selection): Query<Selection>,
|
||||
) -> Response {
|
||||
show(state, headers, PageInputs::for_selection(selection), Page::Columns).await
|
||||
show(state, headers, PageInputs::for_selection(selection), Page::AddColumns).await
|
||||
}
|
||||
|
||||
/// GET /admin/tables/presentation
|
||||
pub(crate) async fn presentation_page(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
Query(selection): Query<Selection>,
|
||||
) -> Response {
|
||||
show(state, headers, PageInputs::for_selection(selection), Page::Presentation).await
|
||||
}
|
||||
|
||||
/// GET /admin/tables/delete
|
||||
@@ -151,7 +164,7 @@ async fn show(state: AppState, headers: HeaderMap, inputs: PageInputs, page: Pag
|
||||
|
||||
// ── The writes ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// POST /admin/tables/columns/builder — staging a column to append.
|
||||
/// POST /admin/tables/columns/add/builder — staging a column to append.
|
||||
///
|
||||
/// Nothing is written here; the panel is swapped back with the column added,
|
||||
/// removed, or its index toggled, exactly as the Add-table builder works.
|
||||
@@ -194,14 +207,14 @@ pub(crate) async fn update_columns(
|
||||
|
||||
match load_page(state, &headers, inputs).await {
|
||||
Ok(mut loaded) => {
|
||||
loaded.active = Page::Columns.name();
|
||||
loaded.active = Page::AddColumns.name();
|
||||
Html(ui::render_column_panel(&loaded)).into_response()
|
||||
}
|
||||
Err(error) => load_error_response(&headers, error),
|
||||
}
|
||||
}
|
||||
|
||||
/// POST /admin/tables/columns — AddTableColumns.
|
||||
/// POST /admin/tables/columns/add — AddTableColumns.
|
||||
pub(crate) async fn add_columns(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
@@ -232,7 +245,7 @@ pub(crate) async fn add_columns(
|
||||
state,
|
||||
headers,
|
||||
inputs,
|
||||
Page::Columns,
|
||||
Page::AddColumns,
|
||||
message,
|
||||
)
|
||||
.await;
|
||||
@@ -246,7 +259,7 @@ pub(crate) async fn add_columns(
|
||||
state,
|
||||
headers,
|
||||
inputs,
|
||||
Page::Columns,
|
||||
Page::AddColumns,
|
||||
message,
|
||||
)
|
||||
.await;
|
||||
@@ -254,7 +267,7 @@ pub(crate) async fn add_columns(
|
||||
// The same checks the server runs, applied to a draft that may have been
|
||||
// rebuilt from a posted form rather than through the panel.
|
||||
if let Err(message) = inputs.columns.validate(Locale::from_headers(&headers)) {
|
||||
return refuse(state, headers, inputs, Page::Columns, message).await;
|
||||
return refuse(state, headers, inputs, Page::AddColumns, message).await;
|
||||
}
|
||||
|
||||
let request = AddTableColumnsRequest {
|
||||
@@ -283,7 +296,7 @@ pub(crate) async fn add_columns(
|
||||
));
|
||||
// The columns are the table's now, so the panel starts empty.
|
||||
inputs.columns = crate::schema::ColumnDraft::for_append(catalog);
|
||||
respond(state, headers, inputs, Page::Columns, StatusCode::OK).await
|
||||
respond(state, headers, inputs, Page::AddColumns, StatusCode::OK).await
|
||||
}
|
||||
Ok(response) => {
|
||||
let message = response.into_inner().sql;
|
||||
@@ -295,10 +308,10 @@ pub(crate) async fn add_columns(
|
||||
} else {
|
||||
message
|
||||
};
|
||||
refuse(state, headers, inputs, Page::Columns, message).await
|
||||
refuse(state, headers, inputs, Page::AddColumns, message).await
|
||||
}
|
||||
Err(error) => {
|
||||
refuse(state, headers, inputs, Page::Columns, error.message().to_string()).await
|
||||
refuse(state, headers, inputs, Page::AddColumns, error.message().to_string()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +344,7 @@ pub(crate) async fn set_column_presentation(
|
||||
state,
|
||||
headers,
|
||||
inputs,
|
||||
Page::Columns,
|
||||
Page::Presentation,
|
||||
message,
|
||||
)
|
||||
.await;
|
||||
@@ -378,7 +391,7 @@ pub(crate) async fn set_column_presentation(
|
||||
table: form.table,
|
||||
..Default::default()
|
||||
};
|
||||
respond(state, headers, inputs, Page::Columns, StatusCode::OK).await
|
||||
respond(state, headers, inputs, Page::Presentation, StatusCode::OK).await
|
||||
}
|
||||
Ok(response) => {
|
||||
let message = response.into_inner().message;
|
||||
@@ -390,10 +403,10 @@ pub(crate) async fn set_column_presentation(
|
||||
} else {
|
||||
message
|
||||
};
|
||||
refuse(state, headers, inputs, Page::Columns, message).await
|
||||
refuse(state, headers, inputs, Page::Presentation, message).await
|
||||
}
|
||||
Err(error) => {
|
||||
refuse(state, headers, inputs, Page::Columns, error.message().to_string()).await
|
||||
refuse(state, headers, inputs, Page::Presentation, error.message().to_string()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! What can be done to a table once it exists, as one page per decision.
|
||||
//!
|
||||
//! Adding columns, dropping the table, copying its profile, generating tables
|
||||
//! from a template and reading the rename history are five different jobs, and
|
||||
//! they used to be five panels stacked on one `/admin/table-definition`
|
||||
//! Adding columns, presenting columns, dropping the table, copying its profile,
|
||||
//! generating tables from a template and reading the rename history are six
|
||||
//! different jobs, and they used to be panels stacked on one `/admin/table-definition`
|
||||
//! workspace — which meant that after creating a table you landed on a screen
|
||||
//! where finding the delete form meant scrolling past five other forms.
|
||||
//!
|
||||
@@ -30,13 +30,18 @@ use crate::AppState;
|
||||
pub(crate) fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
// Table-scoped.
|
||||
.route("/admin/tables/columns", get(logic::columns_page))
|
||||
.route("/admin/tables/columns", post(logic::add_columns))
|
||||
.route(
|
||||
"/admin/tables/columns/builder",
|
||||
"/admin/tables/columns/add",
|
||||
get(logic::add_columns_page).post(logic::add_columns),
|
||||
)
|
||||
.route(
|
||||
"/admin/tables/columns/add/builder",
|
||||
post(logic::update_columns),
|
||||
)
|
||||
.route("/admin/tables/presentation", post(logic::set_column_presentation))
|
||||
.route(
|
||||
"/admin/tables/presentation",
|
||||
get(logic::presentation_page).post(logic::set_column_presentation),
|
||||
)
|
||||
.route("/admin/tables/delete", get(logic::delete_page))
|
||||
.route("/admin/tables/delete", post(logic::delete_table))
|
||||
// Profile-scoped.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! What the table-definition pages render, and the wire formats they post.
|
||||
//!
|
||||
//! One state type serves all five pages, because each of them needs the same
|
||||
//! One state type serves all table-definition pages, because each of them needs the same
|
||||
//! context: which profile and table is being worked on, and what that table
|
||||
//! currently is. What differs is which panel the page renders, and `active`
|
||||
//! is what says so.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//!
|
||||
//! The pages share a state type and an action switcher, so what differs
|
||||
//! between them here is only which panel they render. The column vocabulary
|
||||
//! is threaded through the two that need it, because askama resolves those
|
||||
//! fields on the struct rather than on `page`.
|
||||
//! is threaded through the add-column templates, because askama resolves
|
||||
//! those fields on the struct rather than on `page`.
|
||||
|
||||
use askama::Template;
|
||||
|
||||
@@ -15,25 +15,25 @@ use crate::{
|
||||
|
||||
use super::state::TableDefinitionPageState;
|
||||
|
||||
/// GET /admin/tables/columns
|
||||
/// GET /admin/tables/columns/add
|
||||
#[derive(Template)]
|
||||
#[template(path = "pages/admin/table_definition/columns.html")]
|
||||
struct ColumnsPage<'a> {
|
||||
#[template(path = "pages/admin/table_definition/add_columns.html")]
|
||||
struct AddColumnsPage<'a> {
|
||||
nav: Nav,
|
||||
page: &'a TableDefinitionPageState,
|
||||
column_types: Vec<String>,
|
||||
temporal_types: Vec<String>,
|
||||
gtin_types: Vec<String>,
|
||||
currency_codes: &'static [&'static str],
|
||||
/// False, as on the fragment: the page embeds both, and the outcome is
|
||||
/// reported once, at the top.
|
||||
/// False, as on the fragment: the page reports the outcome once, at the
|
||||
/// top of the panel containing this fragment.
|
||||
standalone_column_panel: bool,
|
||||
}
|
||||
|
||||
/// The `#table-panel` swap on the columns page.
|
||||
/// The `#table-panel` swap on the add-columns page.
|
||||
#[derive(Template)]
|
||||
#[template(path = "pages/admin/table_definition/columns_panel.html")]
|
||||
struct ColumnsFragment<'a> {
|
||||
#[template(path = "pages/admin/table_definition/add_columns_panel.html")]
|
||||
struct AddColumnsFragment<'a> {
|
||||
nav: Nav,
|
||||
page: &'a TableDefinitionPageState,
|
||||
column_types: Vec<String>,
|
||||
@@ -58,6 +58,22 @@ struct ColumnPanelFragment<'a> {
|
||||
standalone_column_panel: bool,
|
||||
}
|
||||
|
||||
/// GET /admin/tables/presentation
|
||||
#[derive(Template)]
|
||||
#[template(path = "pages/admin/table_definition/presentation.html")]
|
||||
struct PresentationPage<'a> {
|
||||
nav: Nav,
|
||||
page: &'a TableDefinitionPageState,
|
||||
}
|
||||
|
||||
/// The `#table-panel` swap on the column-presentation page.
|
||||
#[derive(Template)]
|
||||
#[template(path = "pages/admin/table_definition/presentation_panel.html")]
|
||||
struct PresentationFragment<'a> {
|
||||
nav: Nav,
|
||||
page: &'a TableDefinitionPageState,
|
||||
}
|
||||
|
||||
/// GET /admin/tables/delete
|
||||
#[derive(Template)]
|
||||
#[template(path = "pages/admin/table_definition/delete.html")]
|
||||
@@ -111,8 +127,8 @@ struct HistoryPage<'a> {
|
||||
page: &'a TableDefinitionPageState,
|
||||
}
|
||||
|
||||
pub(crate) fn render_columns_page(page: &TableDefinitionPageState) -> String {
|
||||
render(&ColumnsPage {
|
||||
pub(crate) fn render_add_columns_page(page: &TableDefinitionPageState) -> String {
|
||||
render(&AddColumnsPage {
|
||||
nav: page.nav.clone(),
|
||||
page,
|
||||
// Asking the draft, so the picker can only ever offer what the draft
|
||||
@@ -125,8 +141,8 @@ pub(crate) fn render_columns_page(page: &TableDefinitionPageState) -> String {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_columns_fragment(page: &TableDefinitionPageState) -> String {
|
||||
render(&ColumnsFragment {
|
||||
pub(crate) fn render_add_columns_fragment(page: &TableDefinitionPageState) -> String {
|
||||
render(&AddColumnsFragment {
|
||||
nav: page.nav.clone(),
|
||||
page,
|
||||
column_types: page.columns.offered_types(),
|
||||
@@ -147,6 +163,20 @@ pub(crate) fn render_column_panel(page: &TableDefinitionPageState) -> String {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_presentation_page(page: &TableDefinitionPageState) -> String {
|
||||
render(&PresentationPage {
|
||||
nav: page.nav.clone(),
|
||||
page,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_presentation_fragment(page: &TableDefinitionPageState) -> String {
|
||||
render(&PresentationFragment {
|
||||
nav: page.nav.clone(),
|
||||
page,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_delete_page(page: &TableDefinitionPageState) -> String {
|
||||
render(&DeletePage {
|
||||
nav: page.nav.clone(),
|
||||
@@ -262,7 +292,7 @@ mod tests {
|
||||
error: None,
|
||||
sql: None,
|
||||
generated: Vec::new(),
|
||||
active: "columns",
|
||||
active: "presentation",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,16 +303,18 @@ mod tests {
|
||||
let mut state = page();
|
||||
|
||||
for (active, html) in [
|
||||
("columns", render_columns_page(&state)),
|
||||
("add-columns", render_add_columns_page(&state)),
|
||||
("presentation", render_presentation_page(&state)),
|
||||
("delete", render_delete_page(&state)),
|
||||
] {
|
||||
state.active = active;
|
||||
assert!(!html.contains("Template error"), "{html}");
|
||||
}
|
||||
|
||||
let html = render_columns_page(&state);
|
||||
let html = render_presentation_page(&state);
|
||||
for route in [
|
||||
"/admin/tables/columns?profile=billing",
|
||||
"/admin/tables/columns/add?profile=billing",
|
||||
"/admin/tables/presentation?profile=billing",
|
||||
"/admin/tables/delete?profile=billing",
|
||||
"/admin/profiles/copy?profile=billing",
|
||||
"/admin/tables/from-template?profile=billing",
|
||||
@@ -308,7 +340,7 @@ mod tests {
|
||||
assert!(html.contains("/admin/tables/delete"));
|
||||
assert!(html.contains("Type <code>invoice</code> to confirm"));
|
||||
// The other writes are links in the switcher, not forms on the page.
|
||||
assert!(!html.contains("/admin/tables/presentation"));
|
||||
assert!(!html.contains(r#"hx-post="/admin/tables/presentation""#));
|
||||
assert!(!html.contains("/admin/profiles/copy?profile=billing\" method"));
|
||||
}
|
||||
|
||||
@@ -368,13 +400,16 @@ mod tests {
|
||||
assert!(html.contains("backend's own"));
|
||||
assert!(!html.contains(r#"name="confirm_table_name""#));
|
||||
|
||||
let html = render_columns_page(&state);
|
||||
assert!(!html.contains("/admin/tables/presentation"));
|
||||
let html = render_add_columns_page(&state);
|
||||
assert!(!html.contains(r#"id="column-form""#));
|
||||
|
||||
let html = render_presentation_page(&state);
|
||||
assert!(!html.contains(r#"name="aliases""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_presentation_posts_stable_ids_aliases_and_order_controls() {
|
||||
let html = render_columns_page(&page());
|
||||
let html = render_presentation_page(&page());
|
||||
|
||||
assert!(html.contains(r#"hx-post="/admin/tables/presentation""#), "{html}");
|
||||
assert!(html.contains(r#"name="column_ids" value="1""#), "{html}");
|
||||
@@ -383,6 +418,17 @@ mod tests {
|
||||
assert!(html.contains(r#"name="action" value="save""#), "{html}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adding_columns_and_presentation_are_separate_pages() {
|
||||
let add_html = render_add_columns_page(&page());
|
||||
assert!(add_html.contains(r#"id="column-form""#), "{add_html}");
|
||||
assert!(!add_html.contains(r#"name="aliases""#), "{add_html}");
|
||||
|
||||
let presentation_html = render_presentation_page(&page());
|
||||
assert!(presentation_html.contains(r#"name="aliases""#), "{presentation_html}");
|
||||
assert!(!presentation_html.contains(r#"id="column-form""#), "{presentation_html}");
|
||||
}
|
||||
|
||||
/// The profile-wide pages need only a profile, and say so by still
|
||||
/// rendering with no table selected.
|
||||
#[test]
|
||||
@@ -429,7 +475,7 @@ mod tests {
|
||||
let html = render_column_panel(&state);
|
||||
|
||||
assert!(!html.contains("Template error"), "{html}");
|
||||
assert!(html.contains("/admin/tables/columns/builder"));
|
||||
assert!(html.contains("/admin/tables/columns/add/builder"));
|
||||
// Escaped, because it is an attribute: `&` is what a browser reads
|
||||
// back as the `&` separating the two parameters.
|
||||
assert!(html.contains("?profile=billing&table=invoice"));
|
||||
@@ -548,10 +594,10 @@ mod tests {
|
||||
#[test]
|
||||
fn a_failure_is_shown_as_a_dialog_as_well_as_an_alert() {
|
||||
let mut state = page();
|
||||
assert!(!render_columns_fragment(&state).contains(r#"role="dialog""#));
|
||||
assert!(!render_add_columns_fragment(&state).contains(r#"role="dialog""#));
|
||||
|
||||
state.error = Some("That column already exists.".to_string());
|
||||
let html = render_columns_fragment(&state);
|
||||
let html = render_add_columns_fragment(&state);
|
||||
assert!(html.contains(r#"role="dialog""#));
|
||||
// Once in the inline alert, once in the dialog — and not a third time
|
||||
// from the column panel the fragment embeds.
|
||||
@@ -580,7 +626,7 @@ mod tests {
|
||||
state.status = Some("1 column added to `invoice`.".to_string());
|
||||
state.sql = Some("ALTER TABLE \"billing\".\"invoice\" ADD COLUMN …".to_string());
|
||||
|
||||
let html = render_columns_fragment(&state);
|
||||
let html = render_add_columns_fragment(&state);
|
||||
|
||||
assert!(html.contains("ALTER TABLE"));
|
||||
assert!(html.contains("1 column added"));
|
||||
@@ -591,7 +637,7 @@ mod tests {
|
||||
let mut state = page();
|
||||
state.selection.profile = "__global".to_string();
|
||||
|
||||
let html = render_columns_page(&state);
|
||||
let html = render_presentation_page(&state);
|
||||
|
||||
assert!(!html.contains("Template error"), "{html}");
|
||||
assert!(html.contains("Global — all profiles"));
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
{% if page.selected_table.as_deref() == Some(table.name.as_str()) %}
|
||||
<div class="browser-actions">
|
||||
{% if page.can_manage_tables && !table.is_system() %}
|
||||
<a href="/admin/tables/columns?profile={{ page.selected_profile.as_deref().unwrap_or_default() }}&table={{ table.name }}">{{ nav.tr("admin-add-rename-columns") }}</a>
|
||||
<a href="/admin/tables/presentation?profile={{ page.selected_profile.as_deref().unwrap_or_default() }}&table={{ table.name }}">{{ nav.tr("admin-column-presentation") }}</a>
|
||||
<a class="danger-action" href="/admin/tables/delete?profile={{ page.selected_profile.as_deref().unwrap_or_default() }}&table={{ table.name }}">{{ nav.tr("admin-delete-table") }}</a>
|
||||
{% endif %}
|
||||
<a href="/permissions/grants">{{ nav.tr("admin-who-may-use-it") }}</a>
|
||||
|
||||
18
web/templates/pages/admin/table_definition/add_columns.html
Normal file
18
web/templates/pages/admin/table_definition/add_columns.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{# GET /admin/tables/columns/add — crate::pages::admin::table_definition::ui::AddColumnsPage #}
|
||||
{% extends "ui/base.html" %}
|
||||
|
||||
{% block title %}{{ nav.tr("td-add-columns-title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% include "pages/admin/table_definition/context.html" %}
|
||||
|
||||
<div id="table-panel" aria-live="polite">
|
||||
{% include "pages/admin/table_definition/add_columns_panel.html" %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<datalist id="currency-codes">
|
||||
{% for code in currency_codes %}<option value="{{ code }}"></option>{% endfor %}
|
||||
</datalist>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{# The add-columns page body — crate::pages::admin::table_definition::ui::AddColumnsFragment. #}
|
||||
{% include "pages/admin/table_definition/feedback.html" %}
|
||||
|
||||
{% if page.table_is_writable() %}
|
||||
<section class="panel">
|
||||
<h2>{{ nav.tr("td-add-columns-to") }} <code>{{ page.selection.table }}</code></h2>
|
||||
<p class="hint">{{ nav.tr("td-append-hint") }}</p>
|
||||
<form id="column-form"
|
||||
hx-post="/admin/tables/columns/add{{ page.selection.query() }}"
|
||||
hx-target="#table-panel" hx-swap="innerHTML"
|
||||
hx-disabled-elt="button[type=submit]">
|
||||
<div id="column-panel">
|
||||
{% include "pages/admin/table_definition/column_panel.html" %}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
@@ -29,7 +29,7 @@
|
||||
</label>
|
||||
<label>{{ nav.tr("td-column-type") }}
|
||||
<select name="column_type_input"
|
||||
hx-post="/admin/tables/columns/builder{{ page.selection.query() }}"
|
||||
hx-post="/admin/tables/columns/add/builder{{ page.selection.query() }}"
|
||||
hx-trigger="change" hx-include="#column-form"
|
||||
hx-target="#column-panel" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "refresh"}'>
|
||||
@@ -157,7 +157,7 @@
|
||||
</div>
|
||||
|
||||
<button type="button" class="secondary"
|
||||
hx-post="/admin/tables/columns/builder{{ page.selection.query() }}"
|
||||
hx-post="/admin/tables/columns/add/builder{{ page.selection.query() }}"
|
||||
hx-include="#column-form" hx-target="#column-panel" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "add-column"}'>{{ nav.tr("td-stage-column") }}</button>
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
there is no column of its own name to index. #}
|
||||
{% if page.columns.is_indexable(*loop.index0) %}
|
||||
<button type="button" class="toggle"
|
||||
hx-post="/admin/tables/columns/builder{{ page.selection.query() }}"
|
||||
hx-post="/admin/tables/columns/add/builder{{ page.selection.query() }}"
|
||||
hx-include="#column-form" hx-target="#column-panel" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "toggle-index", "index": "{{ loop.index0 }}"}'>
|
||||
{% if column.indexed %}[x]{% else %}[ ]{% endif %}
|
||||
@@ -194,7 +194,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="danger"
|
||||
hx-post="/admin/tables/columns/builder{{ page.selection.query() }}"
|
||||
hx-post="/admin/tables/columns/add/builder{{ page.selection.query() }}"
|
||||
hx-include="#column-form" hx-target="#column-panel" hx-swap="innerHTML"
|
||||
hx-vals='{"action": "remove-column", "index": "{{ loop.index0 }}"}'>{{ nav.tr("common-remove") }}</button>
|
||||
</td>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{# GET /admin/tables/columns — crate::pages::admin::table_definition::ui::ColumnsPage #}
|
||||
{% extends "ui/base.html" %}
|
||||
|
||||
{% block title %}{{ nav.tr("td-columns-title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% include "pages/admin/table_definition/context.html" %}
|
||||
|
||||
{#
|
||||
One swap target for the page's body. Both writes answer with this markup
|
||||
re-read from the backend, so the screen after a change is the definition
|
||||
as it now is, not the form that was submitted.
|
||||
#}
|
||||
<div id="table-panel" aria-live="polite">
|
||||
{% include "pages/admin/table_definition/columns_panel.html" %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<datalist id="currency-codes">
|
||||
{% for code in currency_codes %}<option value="{{ code }}"></option>{% endfor %}
|
||||
</datalist>
|
||||
{% endblock %}
|
||||
@@ -1,14 +1,13 @@
|
||||
{#
|
||||
The heading and action switcher shared by the five table-definition pages.
|
||||
The heading and action switcher shared by the table-definition pages.
|
||||
Every one of them carries a `page` (crate::pages::admin::table_definition::
|
||||
state::TableDefinitionPageState), whose `active` says which is open.
|
||||
|
||||
These are deliberately separate pages, for the same reason the permission
|
||||
sections are: adding a column, dropping a table, copying a profile and
|
||||
reading its rename history are four different decisions, and stacking them
|
||||
on one screen was what made the old workspace impossible to find anything
|
||||
in. Which links appear follows how much has been selected — the table-wide
|
||||
ones need a table, the profile-wide ones only a profile.
|
||||
sections are: adding columns, presenting them, dropping a table, copying a
|
||||
profile and reading its rename history are different decisions. Which links
|
||||
appear follows how much has been selected — the table-wide ones need a
|
||||
table, the profile-wide ones only a profile.
|
||||
#}
|
||||
<section class="heading">
|
||||
<div>
|
||||
@@ -40,8 +39,11 @@
|
||||
<nav class="tabs" aria-label="{{ nav.tr("td-tab-aria") }}">
|
||||
{% if page.selection.has_table() %}
|
||||
{% if page.table_is_writable() %}
|
||||
<a href="/admin/tables/columns{{ page.selection.query() }}" class="tab {% if page.is("columns") %}selected{% endif %}" {% if page.is("columns") %}aria-current="page"{% endif %}>
|
||||
<span>{{ nav.tr("td-tab-columns") }}</span><small>{{ nav.tr("td-tab-columns-sub") }}</small>
|
||||
<a href="/admin/tables/columns/add{{ page.selection.query() }}" class="tab {% if page.is("add-columns") %}selected{% endif %}" {% if page.is("add-columns") %}aria-current="page"{% endif %}>
|
||||
<span>{{ nav.tr("td-tab-add-columns") }}</span><small>{{ nav.tr("td-tab-add-columns-sub") }}</small>
|
||||
</a>
|
||||
<a href="/admin/tables/presentation{{ page.selection.query() }}" class="tab {% if page.is("presentation") %}selected{% endif %}" {% if page.is("presentation") %}aria-current="page"{% endif %}>
|
||||
<span>{{ nav.tr("td-tab-presentation") }}</span><small>{{ nav.tr("td-tab-presentation-sub") }}</small>
|
||||
</a>
|
||||
<a href="/admin/tables/delete{{ page.selection.query() }}" class="tab {% if page.is("delete") %}selected{% endif %}" {% if page.is("delete") %}aria-current="page"{% endif %}>
|
||||
<span>{{ nav.tr("td-tab-delete") }}</span><small>{{ nav.tr("td-tab-delete-sub") }}</small>
|
||||
|
||||
14
web/templates/pages/admin/table_definition/presentation.html
Normal file
14
web/templates/pages/admin/table_definition/presentation.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{# GET /admin/tables/presentation — crate::pages::admin::table_definition::ui::PresentationPage #}
|
||||
{% extends "ui/base.html" %}
|
||||
|
||||
{% block title %}{{ nav.tr("td-presentation-title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% include "pages/admin/table_definition/context.html" %}
|
||||
|
||||
<div id="table-panel" aria-live="polite">
|
||||
{% include "pages/admin/table_definition/presentation_panel.html" %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -1,29 +1,7 @@
|
||||
{#
|
||||
The columns page's body — crate::pages::admin::table_definition::ui::ColumnsFragment.
|
||||
Both writes on this page swap it, so it carries the outcome as well as the
|
||||
forms.
|
||||
#}
|
||||
{# The column-presentation page body — crate::pages::admin::table_definition::ui::PresentationFragment. #}
|
||||
{% include "pages/admin/table_definition/feedback.html" %}
|
||||
|
||||
{% if page.table_is_writable() %}
|
||||
{#
|
||||
Append columns. The panel stages columns without writing anything; the
|
||||
selection travels in the URL so the posted fields are exactly the ones the
|
||||
Add-table builder posts, and both are decoded by the same code.
|
||||
#}
|
||||
<section class="panel">
|
||||
<h2>{{ nav.tr("td-add-columns-to") }} <code>{{ page.selection.table }}</code></h2>
|
||||
<p class="hint">{{ nav.tr("td-append-hint") }}</p>
|
||||
<form id="column-form"
|
||||
hx-post="/admin/tables/columns{{ page.selection.query() }}"
|
||||
hx-target="#table-panel" hx-swap="innerHTML"
|
||||
hx-disabled-elt="button[type=submit]">
|
||||
<div id="column-panel">
|
||||
{% include "pages/admin/table_definition/column_panel.html" %}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{% if let Some(detail) = page.detail %}
|
||||
<section class="panel">
|
||||
<h2>{{ nav.tr("td-rename-column") }}</h2>
|
||||
Reference in New Issue
Block a user