From e989eb70153bf1734ce82c7f5e1960edd35a23fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=83=BC=E3=82=8B?= Date: Thu, 5 Nov 2026 13:12:21 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E4=B8=A6=E3=81=B3=E3=82=92=E5=A4=89=E3=81=88=E3=80=81=E4=B8=AD?= =?UTF-8?q?=E7=B6=99=E3=82=92=E3=83=98=E3=83=83=E3=83=80=E3=83=BC=E3=81=AE?= =?UTF-8?q?=20LIVE=20=E3=81=AB=E7=A7=BB=E3=81=97=E3=80=81=E5=B1=A5?= =?UTF-8?q?=E6=AD=B4=E3=81=AB=20TPS=20=E3=81=AE=E5=88=97=E3=82=92=E8=B6=B3?= =?UTF-8?q?=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 左のメニューをタイマー・解析・練習の順にし、解析は履歴から開く - 中継は左のメニューから外し、ヘッダーのキューブ名の左に LIVE のリンクを置く - 解析のタブを履歴・統計・AI解析の順にする - 履歴はモードをフルで絞り込んだ状態で開く - 履歴の一覧のメモの列を消し、TPS の列を足す(並べ替えはサーバーの sort=tps、大きい順から) Co-Authored-By: Claude Opus 5.5 --- docs/openapi.yaml | 4 +-- internal/solves/solves.go | 2 ++ internal/solves/solves_test.go | 1 + web/src/App.tsx | 36 +++++++++++----------- web/src/api.ts | 2 +- web/src/components/AnalysisTabs.tsx | 4 +-- web/src/index.css | 46 +++++++++++++++++++++-------- web/src/pages/HistoryPage.tsx | 12 ++++---- 8 files changed, 67 insertions(+), 40 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index ef31cce..efbca50 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -82,8 +82,8 @@ paths: schema: { type: string, example: "splits,analyses" } - name: sort in: query - description: 並べ替えの基準。値の無いもの(DNF など)は最後 - schema: { type: string, enum: [date, time, moves, cross, f2l, oll, pll], default: date } + description: 並べ替えの基準。tps は段階の手数の合計を実測の時間(+2 を含めない)で割った値。値の無いもの(DNF など)は最後 + schema: { type: string, enum: [date, time, moves, tps, cross, f2l, oll, pll], default: date } - { name: order, in: query, description: asc で昇順(既定は降順), schema: { type: string, enum: [asc, desc] } } responses: "200": diff --git a/internal/solves/solves.go b/internal/solves/solves.go index ef05273..31e36a3 100644 --- a/internal/solves/solves.go +++ b/internal/solves/solves.go @@ -349,6 +349,8 @@ var SortKeys = map[string]string{ "date": "solved_at", "time": "CASE penalty WHEN 'DNF' THEN NULL WHEN '+2' THEN total_ms + 2000 ELSE total_ms END", "moves": "(SELECT SUM(move_count) FROM solve_splits WHERE solve_id = solves.id)", + // 1秒あたりの手数(段階の手数の合計 ÷ 実測の時間。+2 は含めない) + "tps": "(SELECT SUM(move_count) * 1000.0 / NULLIF(solves.total_ms, 0) FROM solve_splits WHERE solve_id = solves.id)", "cross": splitSum("'cross'"), "f2l": splitSum("'f2l1', 'f2l2', 'f2l3', 'f2l4'"), "oll": splitSum("'oll'"), diff --git a/internal/solves/solves_test.go b/internal/solves/solves_test.go index a503334..8884144 100644 --- a/internal/solves/solves_test.go +++ b/internal/solves/solves_test.go @@ -194,6 +194,7 @@ func TestListSort(t *testing.T) { {Filter{Sort: "time"}, []int64{4, 2, 1, 3}}, // 降順でも DNF は最後 {Filter{Sort: "moves", Ascending: true}, []int64{2, 1, 4, 3}}, // 段階の無いソルブは最後(新しい順) {Filter{Sort: "oll", Ascending: true}, []int64{1, 2, 4, 3}}, + {Filter{Sort: "tps"}, []int64{1, 2, 4, 3}}, // 38手/10秒 > 16手/9秒(+2 は含めない)、段階の無いソルブは最後 {Filter{Sort: "time", Ascending: true, Limit: 2, Offset: 1}, []int64{2, 4}}, } { if got := ids(tc.f); !slices.Equal(got, tc.want) { diff --git a/web/src/App.tsx b/web/src/App.tsx index b93027b..2514c0d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -14,7 +14,10 @@ import { TimerPage } from './pages/TimerPage' import { SettingsProvider } from './settings/SettingsProvider' import { setNavCollapsed, useNavCollapsed } from './ui/prefs' -/** 左のメニュー。match に当てはまるパスでも選択中にする(解析は統計・履歴・解析の3つのタブをまとめる)。 */ +/** + * 左のメニュー。match に当てはまるパスでも選択中にする(解析は履歴・統計・AI解析の3つのタブをまとめ、履歴から開く)。 + * 中継はヘッダーの LIVE から開く。 + */ const NAV: { to: string; label: string; icon: ReactNode; end?: boolean; match?: RegExp }[] = [ { to: '/', @@ -27,6 +30,12 @@ const NAV: { to: string; label: string; icon: ReactNode; end?: boolean; match?: ), }, + { + to: '/history', + label: '解析', + match: /^\/(stats|history|analyses)(\/|$)/, + icon: , + }, { to: '/practice', label: '練習', @@ -38,22 +47,6 @@ const NAV: { to: string; label: string; icon: ReactNode; end?: boolean; match?: ), }, - { - to: '/live', - label: '中継', - icon: ( - <> - - - - ), - }, - { - to: '/stats', - label: '解析', - match: /^\/(stats|history|analyses)(\/|$)/, - icon: , - }, ] /** 左のメニューを重ねて開く(畳めない)画面幅。CSS の @media と合わせる。 */ @@ -118,6 +111,15 @@ export default function App() { Cubalyst
+ `live-link ${isActive ? 'active' : ''}`} + title="中継:キューブをつないだブラウザのタイマー画面を映す" + aria-label="中継" + > +
diff --git a/web/src/api.ts b/web/src/api.ts index baa7ca5..e1695ff 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -55,7 +55,7 @@ export type Solve = { export type NewSolve = Omit /** 一覧の並べ替えの基準。time は +2 込み、DNF と値の無いソルブ(段階が無いなど)はいつも最後。 */ -export type SolveSort = 'date' | 'time' | 'moves' | 'cross' | 'f2l' | 'oll' | 'pll' +export type SolveSort = 'date' | 'time' | 'moves' | 'tps' | 'cross' | 'f2l' | 'oll' | 'pll' export type SolveFilter = { mode?: SolveMode diff --git a/web/src/components/AnalysisTabs.tsx b/web/src/components/AnalysisTabs.tsx index d4958de..8134d40 100644 --- a/web/src/components/AnalysisTabs.tsx +++ b/web/src/components/AnalysisTabs.tsx @@ -1,12 +1,12 @@ import { NavLink } from 'react-router' const TABS = [ - { to: '/stats', label: '統計' }, { to: '/history', label: '履歴' }, + { to: '/stats', label: '統計' }, { to: '/analyses', label: 'AI解析' }, ] -/** 「解析」の中のタブ(統計・履歴・AI解析)。 */ +/** 「解析」の中のタブ(履歴・統計・AI解析)。 */ export function AnalysisTabs() { return (