From 22ee28ba03e580b557686f2df50a9be36d284fbb Mon Sep 17 00:00:00 2001 From: KoyoNoguchi Date: Fri, 23 Oct 2026 12:31:32 +0900 Subject: [PATCH] Add a settings page for profit display and default stack - Per-device toggle to hide your own net and the net ranking at tables - Default starting stack set in settings; creating a table saves the stack, SB and BB used as the next defaults - Number fields on create and settings select all on focus Co-Authored-By: Claude Opus 5.5 --- public/assets/common.js | 40 ++++++++++++++++++++++++++++- public/assets/create.js | 14 +++++++++- public/assets/settings.js | 34 ++++++++++++++++++++++++ public/assets/style.css | 3 +++ public/assets/table.js | 15 +++++++---- public/create.html | 2 +- public/lobby.html | 5 ++++ public/settings.html | 54 +++++++++++++++++++++++++++++++++++++++ server/index.js | 1 + 9 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 public/assets/settings.js create mode 100644 public/settings.html diff --git a/public/assets/common.js b/public/assets/common.js index a9a9776..9a9f3bc 100644 --- a/public/assets/common.js +++ b/public/assets/common.js @@ -28,8 +28,46 @@ } return t; }, + // テーブルで自分の収支を表示するか(設定画面で切り替える。既定は表示) + get showNet() { return lsGet('poker.showNet') !== '0'; }, + set showNet(v) { lsSet('poker.showNet', v ? '1' : '0'); }, + // テーブル作成画面の開始スタックの初期値(設定画面で変えられ、テーブルを作るたびに前回値で上書きする) + get defaultStack() { + const n = Number(lsGet('poker.defaultStack')); + return Number.isInteger(n) && n >= 10 && n <= 100000000 ? n : 1000; + }, + set defaultStack(v) { lsSet('poker.defaultStack', String(v)); }, + // 前回テーブルを作ったときの SB/BB と、そのときの開始スタック { stack, sb, bb } + get lastBlinds() { + try { + const b = JSON.parse(lsGet('poker.lastBlinds')); + return b && [b.stack, b.sb, b.bb].every(Number.isInteger) && b.sb >= 1 && b.bb > b.sb && b.bb <= b.stack ? b : null; + } catch { + return null; + } + }, + set lastBlinds(v) { lsSet('poker.lastBlinds', JSON.stringify(v)); }, }; + // 数値の入力欄: フォーカスしたら全体を選択して、そのまま打ち替えられるようにする + function selectOnFocus(el) { + let skipMouseUp = false; + el.addEventListener('focus', () => { + el.select(); + skipMouseUp = true; + // iOS Safari はタップの処理の後で選択が外れるので、もう一度選ぶ + setTimeout(() => { + if (document.activeElement === el) el.select(); + }, 0); + }); + // クリックで入ったときは、離した位置にカーソルが置かれて選択が外れるのを防ぐ + el.addEventListener('mouseup', (e) => { + if (skipMouseUp) e.preventDefault(); + skipMouseUp = false; + }); + el.addEventListener('keydown', () => { skipMouseUp = false; }); + } + function esc(s) { return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } @@ -233,5 +271,5 @@ FINISHING: 'FINISHING', }; - window.App = { Store, esc, chips, toast, confirm: confirmDialog, api, copyText, connectSocket, safeNext, PHASE_LABELS }; + window.App = { Store, esc, chips, toast, selectOnFocus, confirm: confirmDialog, api, copyText, connectSocket, safeNext, PHASE_LABELS }; })(); diff --git a/public/assets/create.js b/public/assets/create.js index c37cb58..8d74c95 100644 --- a/public/assets/create.js +++ b/public/assets/create.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - const { Store, api, chips } = App; + const { Store, api, chips, selectOnFocus } = App; if (!Store.name) { location.replace('/'); return; @@ -109,6 +109,7 @@ timerInput.addEventListener('change', refresh); intervalInput.addEventListener('input', refresh); form.querySelectorAll('input[type="radio"]').forEach((el) => el.addEventListener('change', refresh)); + [stackInput, sbInput, bbInput, intervalInput].forEach(selectOnFocus); $('reroll').addEventListener('click', suggestName); Fluid.Segmented(form.querySelector('.segmented.numbers')); Fluid.navBar($('nav'), $('large-title')); @@ -152,6 +153,9 @@ cpuCount: Number(radioValue('cpu-count')), token: Store.token, }); + // 次にテーブルを作るときの初期値にする + Store.defaultStack = int(stackInput); + Store.lastBlinds = { stack: int(stackInput), sb: int(sbInput), bb: int(bbInput) }; location.href = '/table/' + encodeURIComponent(id); } catch (err) { errorEl.textContent = err.message; @@ -164,7 +168,15 @@ .then((me) => { $('review-mode').hidden = !me.admin; }) .catch(() => {}); + // 開始スタックは設定(=前回値)。SB/BB は前回と同じ開始スタックなら前回値を引き継ぐ + stackInput.value = Store.defaultStack; applyDefaultBlinds(); + const last = Store.lastBlinds; + if (last && last.stack === Store.defaultStack && (last.sb !== int(sbInput) || last.bb !== int(bbInput))) { + sbInput.value = last.sb; + bbInput.value = last.bb; + blindsTouched = true; + } refresh(); suggestName(); })(); diff --git a/public/assets/settings.js b/public/assets/settings.js new file mode 100644 index 0000000..9ef36fd --- /dev/null +++ b/public/assets/settings.js @@ -0,0 +1,34 @@ +// 設定: この端末だけに保存する(localStorage) +(function () { + 'use strict'; + + const { Store, selectOnFocus } = App; + const $ = (id) => document.getElementById(id); + + Fluid.navBar($('nav'), $('large-title')); + + const showNet = $('show-net'); + showNet.checked = Store.showNet; + showNet.addEventListener('change', () => { + Store.showNet = showNet.checked; + Fluid.haptic(6); + }); + + const stack = $('default-stack'); + stack.value = Store.defaultStack; + selectOnFocus(stack); + // 正しい値のときだけ保存する。外したときに不正なら保存済みの値に戻す + stack.addEventListener('input', () => { + const n = Number(stack.value); + const ok = Number.isInteger(n) && n >= 10 && n <= 100000000; + stack.classList.toggle('invalid', stack.value !== '' && !ok); + if (ok) Store.defaultStack = n; + }); + stack.addEventListener('blur', () => { + stack.classList.remove('invalid'); + stack.value = Store.defaultStack; + }); + stack.addEventListener('keydown', (e) => { + if (e.key === 'Enter') stack.blur(); + }); +})(); diff --git a/public/assets/style.css b/public/assets/style.css index 6e8d1cc..bddcb55 100644 --- a/public/assets/style.css +++ b/public/assets/style.css @@ -468,6 +468,9 @@ a { .tone-purple { background: linear-gradient(180deg, #c86ff0, #9440c2); } +.tone-gray { + background: linear-gradient(180deg, #a1a1a8, #75757c); +} /* セルの中の入力(ラベルが左、値が右) */ .field-cell { diff --git a/public/assets/table.js b/public/assets/table.js index af18ef0..aca6ad0 100644 --- a/public/assets/table.js +++ b/public/assets/table.js @@ -437,8 +437,9 @@ const o = m.options; Fluid.countTo($('my-stack'), m.stack, chips); - const net = m.net; - $('my-net').textContent = `収支 ${net > 0 ? '+' : net < 0 ? '−' : '±'}${chips(Math.abs(net))}`; + // 収支は設定で消せる(この端末だけ) + const net = Store.showNet ? m.net : null; + $('my-net').textContent = net == null ? '' : `収支 ${net > 0 ? '+' : net < 0 ? '−' : '±'}${chips(Math.abs(net))}`; $('my-net').className = net > 0 ? 'plus' : net < 0 ? 'minus' : ''; $('my-bet').textContent = seat.inHand && seat.bet > 0 ? `ベット中 ${chips(seat.bet)}` : ''; $('my-hand').textContent = (seat.inHand && seat.handName) || ''; @@ -642,8 +643,11 @@ net: '収支 = 持っているチップ − バイインの合計(リバイを含む)', }; + // 収支を表示しない設定のときはスタック順だけ + const rankMode = () => (Store.showNet ? rankBy : 'stack'); + function rankingEntries(s) { - const key = rankBy === 'net' ? 'net' : 'held'; + const key = rankMode() === 'net' ? 'net' : 'held'; const list = s.seats.filter(Boolean).sort((a, b) => b[key] - a[key] || a.seat - b.seat); let rank = 0; return list.map((p, i) => { @@ -694,7 +698,7 @@ const sub = [p.seat === s.mySeat && 'あなた', p.bot && 'CPU', p.sittingOut && '離席中', !p.connected && 'オフライン'] .filter(Boolean) .join(' ・ '); - const net = rankBy === 'net'; + const net = rankMode() === 'net'; patch( el, `${rank}` + @@ -712,7 +716,8 @@ rankRows.delete(seat); } slideFrom(before); // 順位が変わった行は元の位置から滑らせる - $('ranking-note').textContent = RANK_NOTE[rankBy]; + $('ranking-tabs').hidden = !Store.showNet; + $('ranking-note').textContent = RANK_NOTE[rankMode()]; if (!s.seats.some(Boolean)) listEl.innerHTML = '
  • まだ誰も座っていません
  • '; } diff --git a/public/create.html b/public/create.html index 8fd04a3..f3c0490 100644 --- a/public/create.html +++ b/public/create.html @@ -88,7 +88,7 @@
    - +
    diff --git a/public/lobby.html b/public/lobby.html index eb3b451..76ffff9 100644 --- a/public/lobby.html +++ b/public/lobby.html @@ -38,6 +38,11 @@ 統計プレイスタイル・VPIP・PFR など + + + 設定収支の表示・開始スタック + +
    diff --git a/public/settings.html b/public/settings.html new file mode 100644 index 0000000..c73932b --- /dev/null +++ b/public/settings.html @@ -0,0 +1,54 @@ + + + + + + + + + 設定 - Hold'em Night + + + + + + +
    +

    設定

    + +
    +
    テーブル
    +
    +
    + 収支を表示 + + + + +
    +
    + +
    + +
    +
    テーブルの作成
    +
    +
    + + +
    +
    + +
    +
    + + + + + + diff --git a/server/index.js b/server/index.js index 9930dd9..f84cbc0 100644 --- a/server/index.js +++ b/server/index.js @@ -92,6 +92,7 @@ app.get('/', page('lobby.html')); app.get('/create', page('create.html')); app.get('/table/:id', page('table.html')); app.get('/stats', page('stats.html')); +app.get('/settings', page('settings.html')); // ---------- 管理者専用: ハンドレンジ学習 ---------- app.get('/range', auth.requireAdmin, page('range.html', ADMIN));