Skip to content

Commit 5fdc1b8

Browse files
committed
シリーズ絞り込みの見た目を、数ではなく中身で決める
12 個までならボタン列、という決め方をやめる。**アイコンで見分けられるかどうか**が 実際の分かれ目で、番組タイトルや作品名のような長い名前は 3〜4 個でも横に並べると 読めない(1 つあたりの幅が要り、折り返した 2 行が隣とくっついて見える)。 逆にアイコンが揃っていれば 12 個でも 20 個でも詰めて並べられる。 観光地はシリーズ 11 個+「未設定」でちょうど 12 と境目に張り付いていて、 シリーズを 1 つ足すだけで見た目が切り替わっていた。 - 判定は canTileSeries(アイコン・画像・1〜2 文字のラベルだけで構成され、 SERIES_FILTER_TILE_MAX(20)以下ならボタン列) - 「未設定」は例外。アイコンを持たないが、シリーズ未設定のスポットが 1 件でも あれば必ず選択肢に入るので、例外にしないとアイコンの揃った種別まで一覧に落ちる - 中身の無いシリーズは名前の 1 文字目を絵の代わりに置く(「未」の下に「未設定」)。 名前だけだと背の高さが揃わず、行の中で 1 つだけ浮いて見えるため あわせて ALLOWED_DEV_ORIGINS の注意を書き足す。**1 つでも書くと書いたものだけが 許可になり、開発機自身のブラウザから開いても真っ白になる** —— HTML は 200 で返るのに /_next が 403 で止まって JS が動かないので、原因が分かりにくい(実際に踏んだ)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent c8297b6 commit 5fdc1b8

5 files changed

Lines changed: 78 additions & 22 deletions

File tree

.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ GOOGLE_CLIENT_SECRET=
2525
# 許可するホスト名・IP(カンマ区切り、ポートは書かない)。スマホの実機確認で必要。
2626
# 未設定のまま別端末から開くと、/_next配下の開発用リソースがクロスオリジンとして
2727
# 403になり画面が真っ白になる(next.config.tsのallowedDevOrigins)。本番では不要。
28-
# ALLOWED_DEV_ORIGINS=192.168.0.10,my-dev-host.example.lan
28+
#
29+
# **1つでも書いたらlocalhostと127.0.0.1も一緒に書くこと。** 設定した時点で
30+
# 「ここに書いたものだけ」になり、開発機自身のブラウザから開いても同じように
31+
# 真っ白になる(画面は200で返るのにJSが動かないため、原因が分かりにくい)。
32+
# ALLOWED_DEV_ORIGINS=localhost,127.0.0.1,192.168.0.10,my-dev-host.example.lan
2933

3034
# 訪問記録の写真の保存先(lib/photoStorage.ts)。
3135
# fs … ローカルのファイルシステム(既定)。Docker運用はこちら。

CLAUDE.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ npm run dev # Next.js開発サーバ
1111
npm run build # 本番ビルド(型チェック込み)
1212
```
1313

14+
LAN内の別端末から開発サーバを開くときは`ALLOWED_DEV_ORIGINS`(`.env`)にホスト名・IPを書く。
15+
**1つでも書いたら`localhost``127.0.0.1`も一緒に書くこと** —— 書いた時点で
16+
「書いたものだけ」が許可になり、**開発機自身のブラウザから開いても画面が真っ白になる**
17+
(HTMLは200で返るのに`/_next`配下が403で止まりJSが動かないため、原因が分かりにくい)。
18+
1419
`npm run dev`/`npm run build`はどちらも`--webpack`を明示している(Next.js 16の既定バンドラーのTurbopackには、bindマウントされた`data``photos`をファイル監視から外す`watchOptions.ignored`相当の設定が無いため。`next.config.ts`のコメント参照)。
1520

1621
どちらにも`predev`/`prebuild``npm run copy-maplibre-worker`(`scripts/copy-maplibre-worker.mjs`)が付いており、MapLibreのワーカースクリプトを`node_modules`から`public/maplibre-gl/`へコピーする(生成物のためgit管理外。理由は下記「MapLibreのワーカースクリプト」)。`next dev`/`next build`を直接叩くとこのコピーが走らないため、地図が真っ白になったらまず`npm run copy-maplibre-worker`を実行すること。
@@ -244,9 +249,10 @@ A〜Eはランクへ移した)。配列の並び順がそのままシリーズ
244249
同じにする(公開スポットの縁取りも常に実線で描く)
245250

246251
絞り込みUI(`components/SeriesFilter.tsx`。地図・一覧の`FilterBar`と「シリーズから探す」タブで共用)は
247-
**シリーズの数で見た目が変わる**`SERIES_FILTER_BUTTONS_MAX`(12)以下なら横並びのボタン列
248-
(`ChoiceRow`。ランクの絞り込みと共通の器)、
249-
それを超えると**検索欄つきの一覧**(`SearchableSeriesFilter`)になる。一覧は選択中のシリーズを
252+
**シリーズの中身で見た目が変わる**(`canTileSeries`)。**アイコン(または1〜2文字のラベル)
253+
だけで構成されていて`SERIES_FILTER_TILE_MAX`(20)以下**なら横並びのボタン列
254+
(`ChoiceRow`。ランクの絞り込みと共通の器。**絵の下にシリーズ名を添える**)、
255+
そうでなければ**検索欄つきの一覧**(`SearchableSeriesFilter`)になる。一覧は選択中のシリーズを
250256
チップで出し、検索欄で部分一致(大文字小文字は無視)に絞った候補をタップでトグルする複数選択で、
251257
描くのは既定で`INITIAL_RESULT_LIMIT`(12)件、「さらに表示」で`SEARCH_RESULT_LIMIT`(60)件まで
252258
(超過分は件数だけ出す)。かつては単一選択の
@@ -257,8 +263,11 @@ A〜Eはランクへ移した)。配列の並び順がそのままシリーズ
257263
入れ子のスクロール領域を指で慣性スクロールさせると選択中の行の青い背景がずれて描かれ、
258264
右端の✓が見えたり見えなかったりしたため。件数を絞ってページ・モーダル側のスクロールに任せる。
259265
**シリーズ数が増える種別を足すときはこのUIで選べるかを確認すること。**
260-
**観光地はいま境目にいる** —— シリーズ11個+「未設定」でちょうど12なので、
261-
シリーズを1つ足すと検索欄つきの一覧に切り替わる。
266+
**判断を数から中身へ変えたのは、境目に張り付く種別があったため** —— 観光地は
267+
シリーズ11個+「未設定」でちょうど12で、1つ足すだけで見た目が切り替わっていた。
268+
それに**長い名前は数が少なくても横に並べると読めない**(番組タイトル・作品名は
269+
3〜4個でも1つあたりの幅が要り、折り返した2行が隣とくっついて見える)。
270+
アイコンで見分けが付くかどうかが、実際の分かれ目になっている。
262271
**選択中はどちらの見た目でも青**(シリーズの色では塗らない。理由は上の「ランク」の節)。
263272

264273
**絞り込みの行(`ChoiceRow`)はシリーズ・ランク・訪問状況で共用し、挙動もそろえてある。**

components/SeriesFilter.tsx

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,48 @@
33
import { useMemo, useState } from "react";
44

55
import type { Series } from "@/lib/types";
6-
import { type SeriesStyleDefinition } from "@/lib/seriesStyle";
6+
import type { SpotMark } from "@/lib/spotStyle";
7+
import { UNSET_SERIES, type SeriesStyleDefinition } from "@/lib/seriesStyle";
78
import { resolveSeriesChip } from "@/lib/spotStyle";
89
import SpotMarkGlyph from "@/components/SpotMarkGlyph";
910
import ChoiceRow, { toggleChoice } from "@/components/ChoiceRow";
1011

11-
/** シリーズの選択肢がこれを超える種別(放送回番号など)はボタン列を並べきれないため検索できる一覧にする */
12-
export const SERIES_FILTER_BUTTONS_MAX = 12;
12+
/**
13+
* ボタン列で並べられる上限。**アイコンで見分けられる種別だけがここまで並べられる。**
14+
* アイコンがあっても数十個になれば探せないので、上限自体は残す
15+
* (それを超える種別は検索できる一覧に回す)。
16+
*/
17+
export const SERIES_FILTER_TILE_MAX = 20;
18+
19+
/** 詰めて並べられる中身の長さ(ラベルが1〜2文字ならアイコンと同じ扱い) */
20+
const TILEABLE_LABEL_MAX = 2;
21+
22+
/**
23+
* ボタン列で並べられるか。**数ではなく中身で決める。**
24+
*
25+
* アイコン(や1〜2文字のラベル)なら、絵で見分けが付くので詰めて並べられる。
26+
* 一方、**番組タイトルや作品名のような長い名前は3〜4個でも横に並べると読めない**
27+
* ——1つあたりの幅が要るうえ、折り返して2行になった名前は隣とくっついて見える。
28+
* そういう種別は数が少なくても検索できる一覧のほうが読みやすい。
29+
*
30+
* かつては「12個まではボタン列」と数だけで決めていたが、観光地(11個)が
31+
* 境目に張り付いていて、シリーズを1つ足すだけで見た目が切り替わっていた。
32+
*/
33+
function canTileSeries(
34+
series: Series[],
35+
seriesStyles: SeriesStyleDefinition[]
36+
): boolean {
37+
if (series.length > SERIES_FILTER_TILE_MAX) return false;
38+
return series.every((r) => {
39+
// 「未設定」はアイコンを持たないが、アプリが出す短い固定の名前なので詰められる
40+
// (シリーズ未設定のスポットが1件でもあれば必ず選択肢に入るため、
41+
// 例外にしないとアイコンの揃った種別まで一覧に落ちる)
42+
if (r === UNSET_SERIES) return true;
43+
const { mark } = resolveSeriesChip(r, seriesStyles);
44+
if (mark.kind === "icon" || mark.kind === "image") return true;
45+
return mark.kind === "text" && mark.text.length <= TILEABLE_LABEL_MAX;
46+
});
47+
}
1348

1449
/**
1550
* シリーズによる複数選択の絞り込みUI。地図・一覧の絞り込み(FilterBar)と
@@ -30,10 +65,10 @@ export default function SeriesFilter({
3065
}) {
3166
if (series.length === 0) return null;
3267

33-
// ボタンを並べきれない種別(放送回番号・作品名など)は、検索できる一覧にする。
68+
// アイコンで見分けられない種別(放送回番号・作品名など)は、検索できる一覧にする。
3469
// かつては単一選択のプルダウンだったが、アニメ聖地のようにシリーズが数百ある
3570
// 種別では目当ての値を探せず、複数選択もできなかった
36-
if (series.length > SERIES_FILTER_BUTTONS_MAX) {
71+
if (!canTileSeries(series, seriesStyles)) {
3772
return (
3873
<SearchableSeriesFilter
3974
series={series}
@@ -51,24 +86,27 @@ export default function SeriesFilter({
5186
<ChoiceRow
5287
options={series.map((r) => {
5388
const { mark } = resolveSeriesChip(r, seriesStyles);
89+
// **中身が無いシリーズ(「未設定」)は名前の1文字目を絵の代わりに置く。**
90+
// 名前だけを出すと、絵のある選択肢と背の高さが揃わず、行の中で1つだけ
91+
// 浮いて見える。「未」の下に「未設定」と並べれば、他と同じ形になる。
92+
const glyph: SpotMark =
93+
mark.kind === "none" ? { kind: "text", text: r.slice(0, 1) } : mark;
5494
return {
5595
value: r,
5696
title: r,
5797
// **絵の下にシリーズ名を添える。** アイコンだけでは何のシリーズか読めない
5898
// (丼の絵が3種類並ぶような種別では特に見分けが付かない)。
59-
// 中身が無いシリーズ(アイコンも文字も未設定)は名前だけを出す ——
60-
// チップは押す対象なので、空の四角では選べない。
6199
// **詰めて並べる`compact`は使わない** —— 名前を出すぶんの幅が要る
62100
// (`compact`はアイコン+左右の余白ぶんしかなく、名前が1文字ずつに潰れる)
63-
content:
64-
mark.kind === "none" ? (
65-
r
66-
) : (
67-
<span className="flex flex-col items-center gap-0.5 leading-tight">
68-
<SpotMarkGlyph mark={mark} alt={r} className="h-4 w-4" />
69-
<span className="text-[10px]">{r}</span>
101+
content: (
102+
<span className="flex flex-col items-center gap-0.5 leading-tight">
103+
{/* 文字の中身も絵と同じ高さに収める(揃えないと行ごとに背がずれる) */}
104+
<span className="flex h-4 items-center justify-center text-sm leading-none">
105+
<SpotMarkGlyph mark={glyph} alt={r} className="h-4 w-4" />
70106
</span>
71-
),
107+
<span className="text-[10px]">{r}</span>
108+
</span>
109+
),
72110
};
73111
})}
74112
selected={selected}

docker-compose.dev.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ services:
6161
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-}
6262
# スマホ実機など、LAN内の別端末から開発サーバを開くときに許可するホスト名・IP
6363
# (カンマ区切り)。未設定だと/_next配下の開発用リソースが403になり真っ白になる
64-
# (next.config.tsのallowedDevOrigins)。手元の.envに書く
64+
# (next.config.tsのallowedDevOrigins)。手元の.envに書く。
65+
# **1つでも書いたらlocalhost・127.0.0.1も一緒に書くこと**(書いたものだけが
66+
# 許可されるので、開発機自身のブラウザから開いても真っ白になる)
6567
ALLOWED_DEV_ORIGINS: ${ALLOWED_DEV_ORIGINS:-}
6668
NODE_ENV: development
6769
ports:

next.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const nextConfig: NextConfig = {
1111
// Next.jsは/_next配下の開発用リソース(JS・CSS)をクロスオリジンとして403で拒み、
1212
// 画面が真っ白になる。許可するホストは環境ごとに違うため、リポジトリに焼き込まず
1313
// 環境変数ALLOWED_DEV_ORIGINS(カンマ区切り。ポートは書かない)から読む。
14+
// **1つでも書くと「書いたものだけ」になる** —— localhost・127.0.0.1を落とすと
15+
// 開発機自身のブラウザからも真っ白になる(画面は200で返るのにJSが動かないので、
16+
// 原因が分かりにくい。実際に踏んだ)。
1417
// 本番(next start)には影響しない設定
1518
allowedDevOrigins: (process.env.ALLOWED_DEV_ORIGINS ?? "")
1619
.split(",")

0 commit comments

Comments
 (0)