Make BUY/SELL a toggle and save pending trade across panel close
- Pressing the open tab now closes the panel (back to entry state) instead of
rebuilding it; pressing again re-opens with the selection restored, so hiding
the panel no longer loses the player's pending trade.
- Save the SELL/BUY maps onto ShopView (_pendingSell/_pendingBuy) on toggle-close
and tab switch; _restorePendingSelection() re-applies them when the panel is
rebuilt. The sub-bar stays live while closed: NET shows the delta, Clear
cancels the saved trade, Confirm executes it.
- preview()/clear()/_afterTrade()/_hasAnySelection() now treat a null tab with a
pending selection as an active trade, so the buyback path is no longer shadowed.
- Rename ScoutPanel.data to report: Phaser's GameObject reserves .data (its Data
manager), and a plain object there crashed GameObject.destroy(). Update all
callers in ShopView, tests, and dev/shop-shot.mjs.
- Simplify the scout price line to credits only ("9 CR") and drop the now-unused
priceSell style from shopui.json; update goods.png/psd.
- Add setSellMap()/setBuyMap() to TradeGrid/BuyList to restore a saved selection
onto freshly rebuilt panels, and cover the toggle flow in shop-ui.test.mjs plus
a toggle probe in dev/shop-shot.mjs.
This commit is contained in:
parent
564b086c6a
commit
54989d3a3a
Binary file not shown.
|
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 706 KiB |
Binary file not shown.
|
|
@ -154,10 +154,6 @@
|
|||
"fontSize": 12,
|
||||
"color": "#ffc94d"
|
||||
},
|
||||
"priceSell": {
|
||||
"fontSize": 12,
|
||||
"color": "#7ce8a4"
|
||||
},
|
||||
"flavor": {
|
||||
"fontSize": 12,
|
||||
"color": "#7d92c4"
|
||||
|
|
|
|||
|
|
@ -451,12 +451,12 @@ const bounds = (o) => {
|
|||
scoutAlpha: sv._scout?.alpha ?? null,
|
||||
sellAlpha: sv.sellGrid?.alpha ?? null,
|
||||
focusId: bl._focusId,
|
||||
data: sv._scout?.data ? {
|
||||
id: sv._scout.data.id,
|
||||
localBuy: sv._scout.data.localBuy,
|
||||
demand: (sv._scout.data.demand ?? []).map((c) => c.label),
|
||||
producer: (sv._scout.data.producer ?? []).map((c) => c.label),
|
||||
exits: (sv._scout.data.exits ?? []).map((e) => ({
|
||||
data: sv._scout?.report ? {
|
||||
id: sv._scout.report.id,
|
||||
localBuy: sv._scout.report.localBuy,
|
||||
demand: (sv._scout.report.demand ?? []).map((c) => c.label),
|
||||
producer: (sv._scout.report.producer ?? []).map((c) => c.label),
|
||||
exits: (sv._scout.report.exits ?? []).map((e) => ({
|
||||
sys: e.systemName, cls: e.clsLabel, price: e.price, profit: e.profit, hops: e.hops,
|
||||
})),
|
||||
} : null,
|
||||
|
|
@ -483,7 +483,7 @@ const bounds = (o) => {
|
|||
await sleep(300); // the 140ms fade-in settles
|
||||
scoutChecks.diag = diag.slice();
|
||||
scoutChecks.open = scoutOpenState();
|
||||
const d0 = sv._scout?.data;
|
||||
const d0 = sv._scout?.report;
|
||||
scoutChecks.openOk = !!d0
|
||||
&& sv._scoutOpen === scouted.id
|
||||
&& (sv._scout.alpha ?? 0) > 0.99
|
||||
|
|
@ -507,7 +507,7 @@ const bounds = (o) => {
|
|||
clickCanvas(round(bl.x + bl.vpX + 40), bl.y + round(otherRow.y0 - bl._scroll));
|
||||
await sleep(300);
|
||||
scoutChecks.swapOk = sv._scoutOpen === otherRow.id
|
||||
&& sv._scout?.data?.id === otherRow.id
|
||||
&& sv._scout?.report?.id === otherRow.id
|
||||
&& (sv.sellGrid.alpha ?? 1) < 0.01;
|
||||
// (d) TAP THE SCOUT ITSELF → closes ("anywhere else").
|
||||
clickCanvas(round(sv._scout.x + sv._scout.width / 2), round(sv._scout.y + sv._scout.height / 2));
|
||||
|
|
@ -517,21 +517,87 @@ const bounds = (o) => {
|
|||
box: { x: boxX, y: boxY },
|
||||
diag,
|
||||
open: scoutChecks.open,
|
||||
swap: { row: otherRow.id, id: sv._scout?.data?.id ?? sv._scoutOpen },
|
||||
swap: { row: otherRow.id, id: sv._scout?.report?.id ?? sv._scoutOpen },
|
||||
checks: { openOk: scoutChecks.openOk, closeOk: scoutChecks.closeOk, swapOk: scoutChecks.swapOk, selfCloseOk: scoutChecks.selfCloseOk },
|
||||
ok: scoutChecks.openOk && scoutChecks.closeOk && scoutChecks.swapOk && scoutChecks.selfCloseOk,
|
||||
};
|
||||
out.pass = out.pass && out.tradeScout.ok;
|
||||
|
||||
// ---- TOGGLE PROBE (BUY/SELL press = open; AGAIN = close the panel) ----
|
||||
// Real button-handler path (_onTabTap — the MenuButton onClick). The sub-
|
||||
// bar probe left 3 BUY rows selected (1,2,1): closing the panel must SAVE
|
||||
// that selection (NET + Confirm stay live), re-opening must RESTORE it,
|
||||
// Clear (closed) must cancel it, and Confirm must work with the panel
|
||||
// closed. (The SELL side is exercised by the Confirm step below.)
|
||||
const tog = {};
|
||||
const savedB = {};
|
||||
for (const [id, q] of bl.buyQty) savedB[id] = q;
|
||||
tog.startTab = sv.tab;
|
||||
// (1) BUY/SELL again → the panel CLOSES; the selection is saved.
|
||||
sv._onTabTap('trade');
|
||||
await sleep(120);
|
||||
tog.closedTab = sv.tab;
|
||||
tog.closedGrid = sv.sellGrid === null;
|
||||
tog.closedList = sv.buyList === null;
|
||||
tog.saved = { sell: sv._pendingSell, buy: sv._pendingBuy };
|
||||
tog.netClosed = sv._netValue?.text ?? null;
|
||||
tog.confirmAlphaClosed = sv._confirmBtn?.alpha ?? null;
|
||||
tog.clearAlphaClosed = sv._clearBtn?.alpha ?? null;
|
||||
// (2) BUY/SELL → the panel re-OPENS with the selection RESTORED.
|
||||
sv._onTabTap('trade');
|
||||
await sleep(200);
|
||||
const bl2 = sv.buyList;
|
||||
tog.reopenTab = sv.tab;
|
||||
const restored = {};
|
||||
for (const [id, q] of Object.entries(savedB)) restored[id] = bl2?.buyQty?.get(id) ?? 0;
|
||||
tog.restored = restored;
|
||||
// (3) Close again, then Clear (closed) → the saved selection is CANCELLED.
|
||||
sv._onTabTap('trade');
|
||||
await sleep(120);
|
||||
sv.clear();
|
||||
tog.afterClearNet = sv._netValue?.text ?? null;
|
||||
tog.afterClearConfirmAlpha = sv._confirmBtn?.alpha ?? null;
|
||||
// (4) A SELL, closed panel, Confirm from the sub-bar (trade lands).
|
||||
sv._onTabTap('trade');
|
||||
await sleep(200);
|
||||
sv.sellGrid?.toggleBay?.(0);
|
||||
sv._onTabTap('trade');
|
||||
await sleep(120);
|
||||
const creditsBefore = gs.ship.credits;
|
||||
tog.confirmClosed = sv.confirm();
|
||||
tog.creditsDelta = gs.ship.credits - creditsBefore;
|
||||
tog.netAfterConfirm = sv._netValue?.text ?? null;
|
||||
tog.buybackEnabled = sv.isBuybackEnabled();
|
||||
// (5) Re-open → FRESH selection (the confirmed trade is gone).
|
||||
sv._onTabTap('trade');
|
||||
await sleep(200);
|
||||
tog.freshSell = sv.sellGrid?.getSellMap?.() ?? {};
|
||||
tog.freshBuy = sv.buyList?.getBuyMap?.() ?? {};
|
||||
const savedBuyOk = tog.saved?.buy && Object.entries(savedB).every(([id, q]) => tog.saved.buy[id] === q);
|
||||
const restoredOk = bl2 && Object.entries(savedB).every(([id, q]) => restored[id] === q);
|
||||
tog.ok = tog.startTab === 'trade'
|
||||
&& tog.closedTab === null && tog.closedGrid && tog.closedList
|
||||
&& savedBuyOk
|
||||
&& tog.netClosed === netText && tog.netClosed !== '0 cr' // NET kept the delta
|
||||
&& (tog.confirmAlphaClosed ?? 0) > 0.9 && (tog.clearAlphaClosed ?? 0) > 0.9
|
||||
&& tog.reopenTab === 'trade' && restoredOk
|
||||
&& tog.afterClearNet === '0 cr' && (tog.afterClearConfirmAlpha ?? 1) < 0.1
|
||||
&& tog.confirmClosed === true && tog.creditsDelta > 0
|
||||
&& tog.netAfterConfirm === '0 cr' && tog.buybackEnabled === true
|
||||
&& Object.keys(tog.freshSell).length === 0
|
||||
&& Object.keys(tog.freshBuy).length === 0;
|
||||
out.toggle = { ...tog, savedB };
|
||||
out.pass = out.pass && out.toggle.ok;
|
||||
|
||||
// Leave the scout OPEN on a good with charted exits (for the screenshot).
|
||||
for (const r of bl.rows) {
|
||||
clickCanvas(round(bl.x + bl.vpX + 40), bl.y + round(r.y0 - bl._scroll));
|
||||
await sleep(280);
|
||||
if (sv._scoutOpen === r.id && (sv._scout.data?.exits?.length ?? 0) > 0) break;
|
||||
if (sv._scoutOpen === r.id && (sv._scout.report?.exits?.length ?? 0) > 0) break;
|
||||
clickCanvas(round(bl.x + bl.vpX + 40), bl.y + round(r.y0 - bl._scroll)); // close again
|
||||
await sleep(280);
|
||||
}
|
||||
out.scoutShot = { openId: sv._scoutOpen, exits: sv._scout?.data?.exits?.length ?? 0 };
|
||||
out.scoutShot = { openId: sv._scoutOpen, exits: sv._scout?.report?.exits?.length ?? 0 };
|
||||
|
||||
out.screen = { w: ss.scale.width, h: ss.scale.height };
|
||||
out.topY = sv.topY ?? null;
|
||||
|
|
|
|||
|
|
@ -637,6 +637,65 @@ console.log('\n== BUYBACK is locked until a sale + Clear resets the selection ==
|
|||
}
|
||||
console.log('LOCK + CLEAR: OK');
|
||||
|
||||
// ===========================================================================
|
||||
console.log('\n== TOGGLE — BUY/SELL presses open the panel; again closes it ==');
|
||||
{
|
||||
const ship = makeShip(10000);
|
||||
ship.cargo.add([A]);
|
||||
const { view } = makeView(ship, { reveal: false });
|
||||
|
||||
// Entry state: panel closed, sub-bar idle.
|
||||
assert.equal(view.tab, null, 'entry state: the panel is closed');
|
||||
assert.equal(view._netValue.text, '0 cr', '… and the NET readout is zero');
|
||||
assert.equal(view._confirmBtn.alpha, 0, '… and Confirm is hidden');
|
||||
|
||||
// Press BUY/SELL (the button handler is _onTabTap) → the panel opens.
|
||||
view._onTabTap('trade');
|
||||
assert.equal(view.tab, 'trade', 'pressing BUY/SELL opens the trade panel');
|
||||
assert.ok(view.sellGrid && view.buyList, '… with both panels built');
|
||||
|
||||
// Build a pending trade: sell A (bay 0) + buy 2 of the stocked good.
|
||||
view.sellGrid.toggleBay(0);
|
||||
view.buyList.bumpBuy(C, 2);
|
||||
const netBefore = view.preview().net;
|
||||
|
||||
// Press BUY/SELL AGAIN → the panel CLOSES (toggle off)…
|
||||
view._onTabTap('trade');
|
||||
assert.equal(view.tab, null, 'pressing BUY/SELL again closes the panel');
|
||||
assert.equal(view.sellGrid, null, '… the SELL grid is torn down');
|
||||
assert.equal(view.buyList, null, '… and the BUY list');
|
||||
|
||||
// … but the pending trade is SAVED, not lost — the sub-bar stays live.
|
||||
assert.deepEqual(view._pendingSell, { [A]: 1 }, '… the SELL selection is saved');
|
||||
assert.deepEqual(view._pendingBuy, { [C]: 2 }, '… and the BUY selection');
|
||||
assert.equal(view.preview().net, netBefore, '… the preview still sees the same trade');
|
||||
assert.equal(view._confirmBtn.alpha, 1, '… Confirm is live on the closed sub-bar');
|
||||
assert.equal(view._clearBtn.alpha, 1, '… and Clear');
|
||||
|
||||
// Clear (with the panel closed) cancels the saved trade.
|
||||
view.clear();
|
||||
assert.equal(view.preview().net, 0, 'Clear (closed) cancels the saved trade');
|
||||
assert.equal(view._confirmBtn.alpha, 0, '… Confirm hides again');
|
||||
|
||||
// Rebuild a sell, close again, and CONFIRM from the closed state.
|
||||
view._onTabTap('trade'); // open again (toggle on)
|
||||
assert.equal(view.tab, 'trade', 'pressing BUY/SELL re-opens the panel');
|
||||
view.sellGrid.toggleBay(0);
|
||||
view._onTabTap('trade'); // close again (toggle off)
|
||||
const creditsBefore = ship.credits;
|
||||
assert.equal(view.confirm(), true, 'Confirm works with the panel closed');
|
||||
assert.ok(ship.credits > creditsBefore, '… and the sale landed (credits up)');
|
||||
assert.equal(view.preview().net, 0, '… the trade is spent');
|
||||
assert.equal(view.isBuybackEnabled(), true, '… and the sale registered for Buyback');
|
||||
|
||||
// Re-open: a FRESH selection (the confirmed trade is gone, not restored).
|
||||
view._onTabTap('trade');
|
||||
assert.equal(view.tab, 'trade', '… the panel re-opens');
|
||||
assert.deepEqual(view.sellGrid.getSellMap(), {}, '… with no carry-over sell selection');
|
||||
assert.deepEqual(view.buyList.getBuyMap(), {}, '… and no carry-over buy selection');
|
||||
}
|
||||
console.log('TOGGLE: OK');
|
||||
|
||||
// ===========================================================================
|
||||
console.log('\n== TRADE SCOUT — tap a BUY row: the cargo grid swaps for the report ==');
|
||||
{
|
||||
|
|
@ -658,10 +717,10 @@ console.log('\n== TRADE SCOUT — tap a BUY row: the cargo grid swaps for the re
|
|||
assert.equal(view._scout.alpha, 1, 'the report is visible (the stub tween lands on alpha 1)');
|
||||
assert.equal(view.sellGrid.alpha, 0, '… the cargo grid steps aside (alpha 0)');
|
||||
assert.equal(view.buyList._focusId, row.id, '… the row keeps its focus stroke');
|
||||
assert.equal(view._scout.data.id, row.id, '… and it shows THAT good');
|
||||
assert.equal(view._scout.report.id, row.id, '… and it shows THAT good');
|
||||
|
||||
// The report: identity + baseline + charted exits + profit math.
|
||||
const d = view._scout.data;
|
||||
const d = view._scout.report;
|
||||
assert.equal(d.localBuy, row.price, 'the profit baseline is this shop\'s buy price for the row');
|
||||
assert.ok(Array.isArray(d.demand) && d.demand.every((c) => c.label), 'demand ("sought on") classes carry labels');
|
||||
assert.ok(Array.isArray(d.producer) && d.producer.every((c) => c.label), 'producer ("grown on") classes carry labels');
|
||||
|
|
@ -682,7 +741,7 @@ console.log('\n== TRADE SCOUT — tap a BUY row: the cargo grid swaps for the re
|
|||
const other = view.buyList.rows[1] ?? view.buyList.rows[0];
|
||||
view._onRowTap(other);
|
||||
assert.equal(view._scoutOpen, other.id, 'a different box swaps the report');
|
||||
assert.equal(view._scout.data.id, other.id, '… to that good');
|
||||
assert.equal(view._scout.report.id, other.id, '… to that good');
|
||||
assert.equal(view.sellGrid.alpha, 0, '… the cargo grid stays aside');
|
||||
|
||||
// TAP THE LIST'S DEAD SPACE (null) → closes too ("anywhere else").
|
||||
|
|
|
|||
|
|
@ -704,6 +704,23 @@ export class BuyList extends Phaser.GameObjects.Container {
|
|||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore { goodId: qty } stepper quantities onto the CURRENT stock (the
|
||||
* ShopView re-applies the player's pending trade after the BUY/SELL toggle
|
||||
* rebuilds the panel). Ids no longer in stock are dropped. Call after
|
||||
* refresh() (which resets the quantities).
|
||||
*/
|
||||
setBuyMap(map = {}) {
|
||||
this.buyQty = new Map();
|
||||
for (const [id, q] of Object.entries(map ?? {})) {
|
||||
const n = Math.max(0, Math.floor(Number(q) || 0));
|
||||
if (n > 0 && this.stock.some((s) => s.id === id)) this.buyQty.set(id, n);
|
||||
}
|
||||
this._renderRows();
|
||||
this._emitSelChange();
|
||||
return this;
|
||||
}
|
||||
|
||||
hasSelection() {
|
||||
return this.buyQty.size > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* │ TRADE SCOUT ✕ │
|
||||
* │ ┌────┐ Refined Fuel │
|
||||
* │ │icon│ ENERGY & FUEL · T2 │
|
||||
* │ └────┘ BUY HERE 9 cr │
|
||||
* │ └────┘ 9 CR │
|
||||
* │ The lifeblood… │
|
||||
* │ ────────────────────────── │
|
||||
* │ GROWN ON Gas Giant │
|
||||
|
|
@ -56,7 +56,11 @@ export class ScoutPanel extends Phaser.GameObjects.Container {
|
|||
this.setAlpha?.(0); // hidden until show() + reveal()
|
||||
}
|
||||
|
||||
get data() { return this._data; }
|
||||
// NOTE: this accessor is NOT named `data` — Phaser's GameObject reserves
|
||||
// `.data` (its Data manager), and GameObject.destroy() calls
|
||||
// `this.data.destroy()`. A plain report object there would crash the
|
||||
// destroy pass ("this.data.destroy is not a function").
|
||||
get report() { return this._data; }
|
||||
|
||||
// ------------------------------------------------------------------ shell
|
||||
_buildShell() {
|
||||
|
|
@ -150,17 +154,11 @@ export class ScoutPanel extends Phaser.GameObjects.Container {
|
|||
const mt = s.meta ?? {};
|
||||
add(text(tx, 84, meta, { fontSize: `${mt.fontSize ?? 11}px`, color: mt.color ?? '#7d92c4' })).setOrigin?.(0, 0);
|
||||
|
||||
// local prices (the baseline the exits' profits are measured against)
|
||||
// the buy price — the baseline the exits' profits are measured against.
|
||||
// Credits only; no "bought/sells here" wording.
|
||||
const pb = s.priceBuy ?? {};
|
||||
const priceStr = d.localSell != null
|
||||
? `BUY HERE ${d.localBuy} CR · SELLS HERE ${d.localSell} CR`
|
||||
: `BUY HERE ${d.localBuy} CR · NOT BOUGHT HERE`;
|
||||
// ONE wrapped line: the "not bought here" variant is wider than the
|
||||
// panel at 446px, so wrap instead of running into the buy list.
|
||||
const priceT = add(text(tx, 108, priceStr, { fontSize: `${pb.fontSize ?? 12}px`, color: pb.color ?? '#ffc94d' }));
|
||||
const priceT = add(text(tx, 108, `${d.localBuy} CR`, { fontSize: `${pb.fontSize ?? 12}px`, color: pb.color ?? '#ffc94d' }));
|
||||
priceT?.setOrigin?.(0, 0);
|
||||
priceT?.setWordWrap?.(true);
|
||||
priceT?.setWordWrapWidth?.(W - tx - pad);
|
||||
|
||||
// flavor (full width, under the identity block)
|
||||
const fl = s.flavor ?? {};
|
||||
|
|
|
|||
|
|
@ -92,9 +92,13 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
this.capacity = this.ship.cargo ? this.ship.cargo.capacity : 0;
|
||||
|
||||
// --- session state ---------------------------------------------------
|
||||
this.tab = 'trade'; // 'trade' | 'buyback'
|
||||
this.tab = 'trade'; // 'trade' | 'buyback' | null (closed)
|
||||
this.soldThisSession = []; // [{ id, name, qty, price }] (buyback source)
|
||||
this.buybackSel = new Map(); // goodId -> qty to buy back
|
||||
this._pendingSell = null; // { goodId: qty } — the pending trade's
|
||||
this._pendingBuy = null; // SELL/BUY side, saved when the
|
||||
// BUY/SELL toggle closes the panel
|
||||
// (it survives until Confirm/Clear)
|
||||
this.sellGrid = null;
|
||||
this.buyList = null;
|
||||
this._content = null; // the active content container
|
||||
|
|
@ -136,6 +140,15 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
|
||||
setTab(t) {
|
||||
if (t === 'buyback' && !this.isBuybackEnabled()) return this; // locked
|
||||
// Leaving an OPEN trade panel with a pending trade? Save the selection
|
||||
// (same as the toggle-close) so a tab switch never loses it; _showTrade
|
||||
// re-applies it when the panel comes back.
|
||||
if (this.tab === 'trade' && t !== 'trade') {
|
||||
const s = this.sellGrid?.getSellMap?.() ?? {};
|
||||
const b = this.buyList?.getBuyMap?.() ?? {};
|
||||
this._pendingSell = Object.keys(s).length > 0 ? s : null;
|
||||
this._pendingBuy = Object.keys(b).length > 0 ? b : null;
|
||||
}
|
||||
this.tab = t;
|
||||
this._closeScout(); // the content (and the scout) is rebuilt
|
||||
this._destroyContent();
|
||||
|
|
@ -360,10 +373,43 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
|
||||
/** A tab button press (MenuButton → _onTabTap). */
|
||||
/**
|
||||
* A tab button press (MenuButton → _onTabTap). The tabs are TOGGLES:
|
||||
* pressing the one that's already open closes the panel (back to the
|
||||
* entry state — shop clip + sub-bar); pressing it again re-opens it.
|
||||
* The pending trade selection is SAVED across the close (see
|
||||
* _closePanel) — hiding the panel never loses the trade.
|
||||
*/
|
||||
_onTabTap(id) {
|
||||
if (id === 'trade') this.setTab('trade');
|
||||
else if (id === 'buyback' && this.isBuybackEnabled()) this.setTab('buyback');
|
||||
if (id === 'trade') {
|
||||
if (this.tab === 'trade') this._closePanel();
|
||||
else this.setTab('trade');
|
||||
} else if (id === 'buyback' && this.isBuybackEnabled()) {
|
||||
if (this.tab === 'buyback') this._closePanel();
|
||||
else this.setTab('buyback');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The toggle-close: the panel above the sub-bar hides (back to the
|
||||
* entry state — tab null, content gone, shop clip playing). The player's
|
||||
* pending trade is NOT lost — its SELL/BUY maps are saved onto the view
|
||||
* (_pendingSell/_pendingBuy) and re-applied when the panel re-opens
|
||||
* (_restorePendingSelection). While closed, the sub-bar stays live over
|
||||
* the saved selection: NET shows the delta, Clear cancels it, Confirm
|
||||
* executes it. (The buyback selection is already session-persistent.)
|
||||
*/
|
||||
_closePanel() {
|
||||
if (this.tab === 'trade') {
|
||||
const s = this.sellGrid?.getSellMap?.() ?? {};
|
||||
const b = this.buyList?.getBuyMap?.() ?? {};
|
||||
this._pendingSell = Object.keys(s).length > 0 ? s : null;
|
||||
this._pendingBuy = Object.keys(b).length > 0 ? b : null;
|
||||
}
|
||||
this.tab = null;
|
||||
this._destroyContent(); // kills the panel + scout + focus
|
||||
this._syncPreview(); // NET + Clear/Confirm over the saved sel
|
||||
return this;
|
||||
}
|
||||
|
||||
_refreshTabs() {
|
||||
|
|
@ -437,6 +483,9 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
buyList.refresh({ stock: E.shopFor(this.cls, this.systemId, this.galaxy).buy });
|
||||
sellGrid.onSelectionChange = () => this._syncPreview();
|
||||
buyList.onSelectionChange = () => this._syncPreview();
|
||||
// The BUY/SELL toggle closed the panel WITH a pending trade? Re-apply
|
||||
// it onto these fresh panels (hiding the panel never loses the trade).
|
||||
this._restorePendingSelection();
|
||||
// Tap a BUY row's box (outside the stepper) → the TRADE SCOUT: the cargo
|
||||
// grid is swapped for the row's strategy report (what wants it, where
|
||||
// it's grown, the top charted exits + profit). Tap again / anywhere →
|
||||
|
|
@ -531,24 +580,28 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
const cargo = this.ship.cargo ? this.ship.cargo.snapshot() : [];
|
||||
const capacity = this.capacity;
|
||||
const credits = this.ship.credits;
|
||||
if (this.tab === 'trade') {
|
||||
if (this.tab === 'buyback') {
|
||||
// buyback — priced at the sale price (buyOverrides), a neutral undo.
|
||||
const buyMap = {};
|
||||
const buyOverrides = {};
|
||||
for (const r of this._bbRows()) {
|
||||
if (r.sel > 0) { buyMap[r.id] = r.sel; buyOverrides[r.id] = r.price; }
|
||||
}
|
||||
return E.tradePreview({
|
||||
cls: this.cls, systemId: this.systemId, galaxy: this.galaxy,
|
||||
cargo, capacity, credits,
|
||||
sellMap: this.sellGrid ? this.sellGrid.getSellMap() : {},
|
||||
buyMap: this.buyList ? this.buyList.getBuyMap() : {},
|
||||
sellMap: {}, buyMap, buyOverrides,
|
||||
});
|
||||
}
|
||||
// buyback — priced at the sale price (buyOverrides), a neutral undo.
|
||||
const buyMap = {};
|
||||
const buyOverrides = {};
|
||||
for (const r of this._bbRows()) {
|
||||
if (r.sel > 0) { buyMap[r.id] = r.sel; buyOverrides[r.id] = r.price; }
|
||||
}
|
||||
// The trade — the LIVE panel selection when it's open, the SAVED
|
||||
// selection when the BUY/SELL toggle closed the panel (tab === null;
|
||||
// the trade is still pending, just not visible).
|
||||
const sellMap = this.tab === 'trade' ? (this.sellGrid ? this.sellGrid.getSellMap() : {}) : (this._pendingSell ?? {});
|
||||
const buyMap = this.tab === 'trade' ? (this.buyList ? this.buyList.getBuyMap() : {}) : (this._pendingBuy ?? {});
|
||||
return E.tradePreview({
|
||||
cls: this.cls, systemId: this.systemId, galaxy: this.galaxy,
|
||||
cargo, capacity, credits,
|
||||
sellMap: {}, buyMap, buyOverrides,
|
||||
sellMap, buyMap,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -564,11 +617,15 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
clear() {
|
||||
if (this.tab === 'trade') {
|
||||
if (this.tab === 'buyback') {
|
||||
this.buybackSel = new Map();
|
||||
} else if (this.tab === 'trade') {
|
||||
this.sellGrid?.clearSelection();
|
||||
this.buyList?.clearSelection();
|
||||
} else {
|
||||
this.buybackSel = new Map();
|
||||
// Closed panel — cancel the SAVED pending trade (the toggle-close kept it).
|
||||
this._pendingSell = null;
|
||||
this._pendingBuy = null;
|
||||
}
|
||||
this._syncPreview();
|
||||
return this;
|
||||
|
|
@ -583,13 +640,17 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
_afterTrade(pv) {
|
||||
if (this.tab === 'trade') {
|
||||
if (this.tab === 'buyback') {
|
||||
for (const b of pv.buy) this._reduceSold(b.id, b.qty);
|
||||
this.buybackSel = new Map();
|
||||
} else {
|
||||
// The trade — open tab OR the closed panel (tab === null, selection in
|
||||
// the _pending* store). Record the sale for buyback, reset both.
|
||||
for (const s of pv.sell) this._addSold(s.id, s.qty, s.unit, s.name);
|
||||
this.sellGrid?.clearSelection();
|
||||
this.buyList?.clearSelection();
|
||||
} else {
|
||||
for (const b of pv.buy) this._reduceSold(b.id, b.qty);
|
||||
this.buybackSel = new Map();
|
||||
this._pendingSell = null;
|
||||
this._pendingBuy = null;
|
||||
}
|
||||
this.sellGrid?.refresh({ cargo: this.ship.cargo ? this.ship.cargo.snapshot() : [] });
|
||||
this.refreshHud();
|
||||
|
|
@ -645,8 +706,28 @@ export class ShopView extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
_hasAnySelection() {
|
||||
if (this.tab === 'buyback') return [...this.buybackSel.values()].some((q) => q > 0);
|
||||
if (this.tab === 'trade') return (this.sellGrid?.hasSelection() ?? false) || (this.buyList?.hasSelection() ?? false);
|
||||
return [...this.buybackSel.values()].some((q) => q > 0);
|
||||
// Closed panel: the saved pending trade is the selection.
|
||||
return Object.keys(this._pendingSell ?? {}).length > 0 || Object.keys(this._pendingBuy ?? {}).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-apply the selection _closePanel() saved (the player closed the PANEL
|
||||
* via the BUY/SELL toggle, not the trade). Only called from _showTrade,
|
||||
* right after the fresh panels' refresh() + handler wiring. Clears the
|
||||
* store so a later confirm/clear sees the live panels as the source.
|
||||
*/
|
||||
_restorePendingSelection() {
|
||||
const s = this._pendingSell;
|
||||
const b = this._pendingBuy;
|
||||
this._pendingSell = null;
|
||||
this._pendingBuy = null;
|
||||
if (!s && !b) return this;
|
||||
if (s && Object.keys(s).length > 0) this.sellGrid?.setSellMap?.(s);
|
||||
if (b && Object.keys(b).length > 0) this.buyList?.setBuyMap?.(b);
|
||||
this._syncPreview();
|
||||
return this;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -276,6 +276,26 @@ export class TradeGrid extends Phaser.GameObjects.Container {
|
|||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a { goodId: qty } selection onto the CURRENT bays (the ShopView
|
||||
* re-applies the player's pending trade after the BUY/SELL toggle rebuilds
|
||||
* the panel). Checks the first N filled bays of each good; ids no longer
|
||||
* in the hold are dropped. Call after refresh() (which resets selection).
|
||||
*/
|
||||
setSellMap(map = {}) {
|
||||
this.sellSel = new Set();
|
||||
for (const [id, want] of Object.entries(map ?? {})) {
|
||||
let need = Math.max(0, Math.floor(Number(want) || 0));
|
||||
if (!need) continue;
|
||||
for (let i = 0; i < this.capacity && need > 0; i++) {
|
||||
if (this._bayIdAt(i) === id) { this.sellSel.add(i); need--; }
|
||||
}
|
||||
}
|
||||
this._renderAll();
|
||||
this._emitSelChange();
|
||||
return this;
|
||||
}
|
||||
|
||||
hasSelection() {
|
||||
return this.sellSel.size > 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue