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 <noreply@anthropic.com>
This commit is contained in:
1 parent
ed3d1e437e
commit
22ee28ba03
9 files changed
+160
-8
No files matched your search
+39
-1
@@ -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 };
|
||||
})();
|
||||
+13
-1
@@ -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();
|
||||
})();
|
||||
@@ -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();
|
||||
});
|
||||
})();
|
||||
@@ -468,6 +468,9 @@ a {
|
||||
.tone-purple {
|
||||
background: linear-gradient(180deg, #c86ff0, #9440c2);
|
||||
}
|
||||
.tone-gray {
|
||||
background: linear-gradient(180deg, #a1a1a8, #75757c);
|
||||
}
|
||||
|
||||
/* セルの中の入力(ラベルが左、値が右) */
|
||||
.field-cell {
|
||||
|
||||
+10
-5
@@ -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,
|
||||
`<span class="rank-no${rank <= 3 ? ` top-${rank}` : ''}">${rank}</span>` +
|
||||
@@ -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 = '<li class="empty">まだ誰も座っていません</li>';
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@
|
||||
<div class="list">
|
||||
<div class="cell field-cell value-right">
|
||||
<label for="stack">開始スタック</label>
|
||||
<input id="stack" type="number" inputmode="numeric" min="10" step="1" value="1000">
|
||||
<input id="stack" type="number" inputmode="numeric" min="10" step="1">
|
||||
</div>
|
||||
<div class="cell field-cell value-right">
|
||||
<label for="sb">スモールブラインド</label>
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
<span class="cell-title">統計<span class="cell-sub">プレイスタイル・VPIP・PFR など</span></span>
|
||||
<svg class="i chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
<a class="cell cell-link" href="/settings">
|
||||
<span class="cell-icon tone-gray"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h9M17 7h3M4 17h3M11 17h9"/><circle cx="15" cy="7" r="2"/><circle cx="9" cy="17" r="2"/></svg></span>
|
||||
<span class="cell-title">設定<span class="cell-sub">収支の表示・開始スタック</span></span>
|
||||
<svg class="i chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="error-text" id="name-error" role="alert"></div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f2f2f7">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>設定 - Hold'em Night</title>
|
||||
<link rel="icon" href="/assets/favicon.ico">
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
</head>
|
||||
<body class="page-settings">
|
||||
<header class="nav-bar" id="nav">
|
||||
<div class="nav-inner">
|
||||
<a class="nav-back" href="/"><svg class="i" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg>ロビー</a>
|
||||
<span class="nav-title"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="wrap">
|
||||
<h1 class="large-title" id="large-title">設定</h1>
|
||||
|
||||
<section class="group">
|
||||
<div class="group-header">テーブル</div>
|
||||
<div class="list">
|
||||
<div class="cell">
|
||||
<span class="cell-title" id="show-net-label">収支を表示</span>
|
||||
<span class="switch">
|
||||
<input id="show-net" type="checkbox" aria-labelledby="show-net-label">
|
||||
<span></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-footer">オフにすると、テーブルで自分の収支と、ランキングの「収支」が表示されなくなります。この端末だけの設定で、他の人の画面は変わりません。</div>
|
||||
</section>
|
||||
|
||||
<section class="group">
|
||||
<div class="group-header">テーブルの作成</div>
|
||||
<div class="list">
|
||||
<div class="cell field-cell value-right">
|
||||
<label for="default-stack">開始スタック</label>
|
||||
<input id="default-stack" type="number" inputmode="numeric" min="10" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-footer" id="stack-hint">テーブルを作るときの初期値です。テーブルを作ると、そのときの値(SB・BB も)が次回の初期値になります。</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/assets/fluid.js"></script>
|
||||
<script src="/assets/common.js"></script>
|
||||
<script src="/assets/settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user