Convert add_purchase.js to TypeScript; drop dead add_edition.js (issue #17)
- Add ts/add_purchase.ts: typed port of add_purchase.js. Replaces getEl with document.querySelector; types the search-select:change CustomEvent detail (SearchSelectChangeDetail / SearchSelectOption) - Point add_purchase / edit_purchase views at compiled dist/add_purchase.js - Delete add_edition.js: no Edition model/view/url/template references it (feature was removed; the script was dead) - Delete the now-superseded add_game.js / add_purchase.js source files - Tighten test_rendered_pages assertions to the dist/ script paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
import { syncSelectInputUntilChanged } from "./utils.js";
|
|
||||||
|
|
||||||
let syncData = [
|
|
||||||
{
|
|
||||||
source: "#id_game",
|
|
||||||
source_value: "dataset.name",
|
|
||||||
target: "#id_name",
|
|
||||||
target_value: "value",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: "#id_game",
|
|
||||||
source_value: "textContent",
|
|
||||||
target: "#id_sort_name",
|
|
||||||
target_value: "value",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
source: "#id_game",
|
|
||||||
source_value: "dataset.year",
|
|
||||||
target: "#id_year_released",
|
|
||||||
target_value: "value",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
syncSelectInputUntilChanged(syncData, "form");
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { syncSelectInputUntilChanged } from "./utils.js";
|
|
||||||
|
|
||||||
let syncData = [
|
|
||||||
{
|
|
||||||
source: "#id_name",
|
|
||||||
source_value: "value",
|
|
||||||
target: "#id_sort_name",
|
|
||||||
target_value: "value",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
syncSelectInputUntilChanged(syncData, "form");
|
|
||||||
@@ -194,7 +194,7 @@ def _pricing_controls() -> Node:
|
|||||||
By default the form's own single Price field is the bundle price. When 2+
|
By default the form's own single Price field is the bundle price. When 2+
|
||||||
games are selected and "Separate price per game" is checked, the per-game
|
games are selected and "Separate price per game" is checked, the per-game
|
||||||
inputs (the general ``selection-fields`` element) take over and the bundle
|
inputs (the general ``selection-fields`` element) take over and the bundle
|
||||||
Price is hidden. Toggle/visibility wiring lives in add_purchase.js; the
|
Price is hidden. Toggle/visibility wiring lives in ts/add_purchase.ts; the
|
||||||
hidden ``pricing_mode`` tells the view which path to take.
|
hidden ``pricing_mode`` tells the view which path to take.
|
||||||
"""
|
"""
|
||||||
return Div(attributes=[("id", "pricing-controls")])[
|
return Div(attributes=[("id", "pricing-controls")])[
|
||||||
@@ -301,7 +301,7 @@ def add_purchase(request: HttpRequest, game_id: int = 0) -> HttpResponse:
|
|||||||
),
|
),
|
||||||
title="Add New Purchase",
|
title="Add New Purchase",
|
||||||
scripts=mark_safe(
|
scripts=mark_safe(
|
||||||
ModuleScript("search_select.js") + ModuleScript("add_purchase.js")
|
ModuleScript("search_select.js") + ModuleScript("dist/add_purchase.js")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ def edit_purchase(request: HttpRequest, purchase_id: int) -> HttpResponse:
|
|||||||
AddForm(form, request=request, additional_row=_purchase_additional_row()),
|
AddForm(form, request=request, additional_row=_purchase_additional_row()),
|
||||||
title="Edit Purchase",
|
title="Edit Purchase",
|
||||||
scripts=mark_safe(
|
scripts=mark_safe(
|
||||||
ModuleScript("search_select.js") + ModuleScript("add_purchase.js")
|
ModuleScript("search_select.js") + ModuleScript("dist/add_purchase.js")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -125,14 +125,14 @@ class RenderedPagesTest(TestCase):
|
|||||||
|
|
||||||
def test_add_game_form(self):
|
def test_add_game_form(self):
|
||||||
html = self.get("games:add_game").content.decode()
|
html = self.get("games:add_game").content.decode()
|
||||||
self.assertIn("add_game.js", html)
|
self.assertIn("dist/add_game.js", html)
|
||||||
self.assertIn("submit_and_redirect", html)
|
self.assertIn("submit_and_redirect", html)
|
||||||
self.assertIn("Submit & Create Purchase", html) # & correctly escaped
|
self.assertIn("Submit & Create Purchase", html) # & correctly escaped
|
||||||
self.assertNoEscapedTags(html)
|
self.assertNoEscapedTags(html)
|
||||||
|
|
||||||
def test_add_purchase_form(self):
|
def test_add_purchase_form(self):
|
||||||
html = self.get("games:add_purchase").content.decode()
|
html = self.get("games:add_purchase").content.decode()
|
||||||
self.assertIn("add_purchase.js", html)
|
self.assertIn("dist/add_purchase.js", html)
|
||||||
self.assertIn("Submit & Create Session", html)
|
self.assertIn("Submit & Create Session", html)
|
||||||
self.assertIn("<tr>", html)
|
self.assertIn("<tr>", html)
|
||||||
self.assertNoEscapedTags(html)
|
self.assertNoEscapedTags(html)
|
||||||
|
|||||||
@@ -1,58 +1,70 @@
|
|||||||
import { getEl, disableElementsWhenTrue, onSwap } from "./utils.js";
|
import { disableElementsWhenTrue, onSwap } from "./utils.js";
|
||||||
|
|
||||||
|
interface SearchSelectOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
data: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchSelectChangeDetail {
|
||||||
|
name: string;
|
||||||
|
values: string[];
|
||||||
|
last: SearchSelectOption | null;
|
||||||
|
}
|
||||||
|
|
||||||
// Switch between a single bundle price and one price per game. The per-game
|
// Switch between a single bundle price and one price per game. The per-game
|
||||||
// inputs are the selection-fields element; this only sets the policy: the
|
// inputs are the selection-fields element; this only sets the policy: the
|
||||||
// hidden pricing_mode the view reads, the element's "active" flag, and whether
|
// hidden pricing_mode the view reads, the element's "active" flag, and whether
|
||||||
// the bundle Price field is shown.
|
// the bundle Price field is shown.
|
||||||
function applyPricingMode(separate) {
|
function applyPricingMode(separate: boolean): void {
|
||||||
const pricingMode = getEl("#id_pricing_mode");
|
const pricingMode = document.querySelector<HTMLInputElement>("#id_pricing_mode");
|
||||||
if (pricingMode) pricingMode.value = separate ? "per_game" : "combined";
|
if (pricingMode) pricingMode.value = separate ? "per_game" : "combined";
|
||||||
|
|
||||||
const selectionFields = document.querySelector("selection-fields");
|
const selectionFields = document.querySelector("selection-fields");
|
||||||
if (selectionFields)
|
if (selectionFields)
|
||||||
selectionFields.setAttribute("active", separate ? "true" : "false");
|
selectionFields.setAttribute("active", separate ? "true" : "false");
|
||||||
|
|
||||||
const priceInput = getEl("#id_price");
|
const priceInput = document.querySelector<HTMLInputElement>("#id_price");
|
||||||
if (priceInput) {
|
if (priceInput) {
|
||||||
const wrapper = priceInput.closest("div");
|
const wrapper = priceInput.closest("div");
|
||||||
if (wrapper) wrapper.classList.toggle("hidden", separate);
|
if (wrapper) wrapper.classList.toggle("hidden", separate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The games field is now a SearchSelect widget (a <div>, not a <select>), so we
|
// The games field is a SearchSelect widget (a <div>, not a <select>), so we
|
||||||
// react to its custom "search-select:change" event instead of syncing a select.
|
// react to its custom "search-select:change" event instead of syncing a select.
|
||||||
document.addEventListener("search-select:change", (event) => {
|
document.addEventListener("search-select:change", (event) => {
|
||||||
if (event.detail.name !== "games") return;
|
const detail = (event as CustomEvent<SearchSelectChangeDetail>).detail;
|
||||||
|
if (detail.name !== "games") return;
|
||||||
|
|
||||||
// Auto-fill platform from the clicked option's data-platform.
|
// Auto-fill platform from the clicked option's data-platform.
|
||||||
const last = event.detail.last;
|
const last = detail.last;
|
||||||
const platformId = last && last.data ? last.data.platform : "";
|
const platformId = last && last.data ? last.data.platform : "";
|
||||||
if (platformId) {
|
if (platformId) {
|
||||||
const platformEl = getEl("#id_platform");
|
const platformElement = document.querySelector<HTMLInputElement>("#id_platform");
|
||||||
if (platformEl) platformEl.value = platformId;
|
if (platformElement) platformElement.value = platformId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The combined/per-game choice is only meaningful with 2+ games. Reveal the
|
// The combined/per-game choice is only meaningful with 2+ games. Reveal the
|
||||||
// checkbox there; below the threshold, fall back to a single bundle price.
|
// checkbox there; below the threshold, fall back to a single bundle price.
|
||||||
const separateRow = getEl("#separate-prices-row");
|
const separateRow = document.querySelector<HTMLElement>("#separate-prices-row");
|
||||||
const multipleGames = event.detail.values.length >= 2;
|
const multipleGames = detail.values.length >= 2;
|
||||||
if (separateRow) separateRow.classList.toggle("hidden", !multipleGames);
|
if (separateRow) separateRow.classList.toggle("hidden", !multipleGames);
|
||||||
if (!multipleGames) {
|
if (!multipleGames) {
|
||||||
const checkbox = getEl("#id_separate_prices");
|
const checkbox = document.querySelector<HTMLInputElement>("#id_separate_prices");
|
||||||
if (checkbox) checkbox.checked = false;
|
if (checkbox) checkbox.checked = false;
|
||||||
applyPricingMode(false);
|
applyPricingMode(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onSwap("#id_separate_prices", (checkbox) => {
|
onSwap("#id_separate_prices", (checkbox) => {
|
||||||
checkbox.addEventListener("change", () => applyPricingMode(checkbox.checked));
|
checkbox.addEventListener("change", () =>
|
||||||
|
applyPricingMode((checkbox as HTMLInputElement).checked)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupElementHandlers() {
|
function setupElementHandlers(): void {
|
||||||
disableElementsWhenTrue("#id_type", "game", [
|
disableElementsWhenTrue("#id_type", "game", ["#id_name", "#id_related_game"]);
|
||||||
"#id_name",
|
|
||||||
"#id_related_game",
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwap("#id_type", (typeSelect) => {
|
onSwap("#id_type", (typeSelect) => {
|
||||||
Reference in New Issue
Block a user