From 00b3a2a3b0af2dc2853b22cf87f05e262c355459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E7=95=91=E3=81=B2=E3=81=A8=E3=81=97?= Date: Sat, 5 Sep 2026 12:07:53 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E7=B5=9E=E3=82=8A=E8=BE=BC=E3=81=BF?= =?UTF-8?q?=E6=AC=84=E3=82=92=E6=97=A2=E5=AE=9A=E3=81=A7=E7=95=B3=E3=82=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 毎回使うものではないのに常に開いていて、その高さぶんグリッドが下へ 押し出されていた。 - 既定は畳む。押すと開く - 開いたかどうかはテーブルをまたいで覚える(一度使う人は続けて使う) - 畳んだ状態で条件が入っていれば、件数をバッジで出す ── 畳んだせいで 絞り込み中だと気づかない、が起きないように イマイチなら戻す前提の変更。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XkRWmyQf9Rt3sYjjkPSq4Y --- templates/base.html | 5 +++++ templates/partials/detail.html | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index 80b305a..c842cc2 100644 --- a/templates/base.html +++ b/templates/base.html @@ -122,6 +122,11 @@ // rows. A stable id keys the x-for so editing one row doesn't disturb the // others; null / not-null operators hide the value input. Alpine.data("filterBuilder", () => ({ + // 既定は畳む。絞り込みは毎回使うものではないのに、常に開いていると + // その高さぶんグリッドが下へ押し出される。開いた状態はテーブルを + // またいで覚える(一度使う人は続けて使うため)。 + open: window.__cli2uiFilterOpen === true, + toggle() { this.open = !this.open; window.__cli2uiFilterOpen = this.open; }, rows: [{ id: 1, col: "", op: "eq", val: "" }], nextId: 2, add() { this.rows.push({ id: this.nextId++, col: "", op: "eq", val: "" }); }, diff --git a/templates/partials/detail.html b/templates/partials/detail.html index 1140034..f6388ad 100644 --- a/templates/partials/detail.html +++ b/templates/partials/detail.html @@ -191,8 +191,21 @@

{{ schema }}.{{ table }}

+ {% comment %}絞り込みは既定で畳む。毎回使うものではないのに常に開いていると、 + その高さぶんグリッドが下へ押し出される。開いたかどうかはテーブルをまたいで + 覚える ── 一度使う人は続けて使う。{% endcomment %} + + -
From 36e82763ce5fe2a82f39821a7a260134d1187d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E7=95=91=E3=81=B2=E3=81=A8=E3=81=97?= Date: Sat, 5 Sep 2026 12:26:52 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AB=E5=85=A5?= =?UTF-8?q?=E3=82=8B=E3=81=B6=E3=82=93=E3=81=A0=E3=81=91=E5=87=BA=E3=81=99?= =?UTF-8?q?=EF=BC=88=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=92=200=20=E6=9C=AC=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1000 行を出しても、画面に見えるのは 16 行ほどだった。残り 98% はスクロール しないと見えず、しかもページ本体とグリッドの両方が動くので、下を見るのに 「まずページを送ってからグリッドを送る」二段階になっていた。 **1000 行をスクロールして目で探す人はいない。** 探すなら絞り込みか SQL を使う。 だから既定を「画面に合わせる」にした。 - body の高さを画面に固定し、main を単一のスクローラにした - グリッドは自分の上端を実測して、main の内側の下端まで伸びる。固定値 (max-h-[60vh])だと、絞り込みを開いたりタブを変えたりで余ったり溢れたりする - 取ってくる行数も画面から決める。900px で 13 行、1200px で 21 行。**どちらも スクロールバー 0 本** - 何行入るかはブラウザでしか分からないので、1 つ目のテーブルだけ描画後に 1 回測り直す。測った数は覚えておき、2 つ目以降は最初から正しい行数で来る。 ずれが 2 行以内なら取り直さない(開くたびに 2 回叩くことになるので) - ブラウザから来た数はそのまま LIMIT に渡さず、10〜200 に丸める - 100 / 500 / 1000 / 5000 は選択肢として残した(コピーして持っていくとき用) サイドバーのテーブル一覧も同じ扱いにした(そちらが画面より高いと、こんどは main 側にスクロールバーが出る)。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XkRWmyQf9Rt3sYjjkPSq4Y --- core/tests.py | 28 ++- core/views/tables.py | 44 +++-- locale/ja/LC_MESSAGES/django.mo | Bin 67656 -> 67748 bytes locale/ja/LC_MESSAGES/django.po | 238 ++++++++++++++------------ templates/base.html | 94 +++++++++- templates/partials/detail.html | 26 ++- templates/partials/filter_result.html | 2 +- templates/partials/query_result.html | 2 +- templates/partials/table_list.html | 5 +- 9 files changed, 295 insertions(+), 144 deletions(-) diff --git a/core/tests.py b/core/tests.py index 1889ff8..f28d4f5 100644 --- a/core/tests.py +++ b/core/tests.py @@ -727,23 +727,37 @@ def _paging(**params): return _paging(RequestFactory().get("/", params)) - def test_defaults(self): - from core.views.tables import DEFAULT_PAGE_SIZE + def test_default_is_fit_to_window(self): + # 既定は「画面に入るぶんだけ」。JS が測る前の 1 回目は FIT_FALLBACK。 + from core.views.tables import FIT_FALLBACK - self.assertEqual(self._paging(), (DEFAULT_PAGE_SIZE, 0)) + self.assertEqual(self._paging(), (FIT_FALLBACK, 0)) - def test_offset_follows_the_page(self): + def test_fit_takes_the_measured_number(self): + self.assertEqual(self._paging(rows="fit", fit="27"), (27, 0)) + + def test_fit_is_clamped(self): + # **ブラウザから来た数をそのまま LIMIT に渡さない。** + from core.views.tables import FIT_MAX, FIT_MIN + + self.assertEqual(self._paging(rows="fit", fit="99999")[0], FIT_MAX) + self.assertEqual(self._paging(rows="fit", fit="0")[0], FIT_MIN) + self.assertEqual(self._paging(rows="fit", fit="-5")[0], FIT_MIN) + + def test_explicit_size_still_works(self): self.assertEqual(self._paging(page="3", rows="100"), (100, 200)) def test_junk_falls_back_quietly(self): # ここでエラーを出しても、利用者にできることが無い。 - from core.views.tables import DEFAULT_PAGE_SIZE + from core.views.tables import FIT_FALLBACK, FIT_MAX, FIT_MIN + allowed = {FIT_FALLBACK, 100, 500, 1000, 5000} for params in [{"page": "abc"}, {"page": "-5"}, {"rows": "99999"}, - {"rows": "0"}, {"rows": "; DROP TABLE x"}]: + {"rows": "0"}, {"rows": "; DROP TABLE x"}, + {"rows": "fit", "fit": "abc"}, {"rows": "fit", "fit": ""}]: with self.subTest(params=params): size, offset = self._paging(**params) - self.assertIn(size, (DEFAULT_PAGE_SIZE, 100, 500, 1000, 5000)) + self.assertTrue(size in allowed or FIT_MIN <= size <= FIT_MAX) self.assertGreaterEqual(offset, 0) diff --git a/core/views/tables.py b/core/views/tables.py index a976e04..0be8633 100644 --- a/core/views/tables.py +++ b/core/views/tables.py @@ -14,22 +14,42 @@ # 1 ページに出す行数。既定は 1000 ── テーブル名を押した人が見たいのは中身で、 # 50 行では「入っているものを見た」ことにならない。選べる値は固定の allow-list に # する(画面から来た数をそのまま LIMIT に渡さないため)。 +# 選べる 1 ページの大きさ。`fit` は「画面に入るぶんだけ」で、これが既定。 +# +# 1000 行を出しても、画面に見えるのは 16 行ほどで、残り 98% はスクロールしないと +# 見えない。**1000 行をスクロールして目で探す人はいない** ── 探すなら絞り込みか +# SQL を使う。だから既定は「ちょうど収まるだけ」にして、スクロールバーを消す。 +# たくさん要る人(コピーして持っていくなど)は選べる。 PAGE_SIZES = (100, 500, 1000, 5000) -DEFAULT_PAGE_SIZE = 1000 +DEFAULT_PAGE_SIZE = "fit" + +# `fit` のときに受け付ける範囲。画面の高さから割り出した数がブラウザから来るので、 +# **そのまま LIMIT には渡さない。** +FIT_MIN, FIT_MAX = 10, 200 +FIT_FALLBACK = 40 # JS が動く前の 1 回目。だいたい 1400×900 で埋まる数 def _paging(request): """`?page=` と `?rows=` を、範囲の決まった (page_size, offset) に直す。 - 壊れた値・負の値・一覧に無い大きさは、黙って既定へ落とす ── ここで - エラーを出しても、利用者にできることが無い。 + `rows=fit` は画面に合わせる指定で、実際の行数は `?fit=` で届く。壊れた値・ + 負の値・一覧に無い大きさは、黙って既定へ落とす ── ここでエラーを出しても、 + 利用者にできることが無い。 """ - try: - size = int(request.GET.get("rows", DEFAULT_PAGE_SIZE)) - except (TypeError, ValueError): - size = DEFAULT_PAGE_SIZE - if size not in PAGE_SIZES: - size = DEFAULT_PAGE_SIZE + raw = request.GET.get("rows", DEFAULT_PAGE_SIZE) + if raw == "fit" or raw is None: + try: + size = int(request.GET.get("fit", FIT_FALLBACK)) + except (TypeError, ValueError): + size = FIT_FALLBACK + size = min(FIT_MAX, max(FIT_MIN, size)) + else: + try: + size = int(raw) + except (TypeError, ValueError): + size = FIT_FALLBACK + if size not in PAGE_SIZES: + size = FIT_FALLBACK try: page = max(1, int(request.GET.get("page", 1))) except (TypeError, ValueError): @@ -92,7 +112,7 @@ def _write_previews(engine, schema, table): def _render_detail(request, connection, schema, table, error=None, notice=None, - page_size=DEFAULT_PAGE_SIZE, offset=0): + page_size=FIT_FALLBACK, offset=0): """Render the table-detail panel: columns, indexes and a row preview. A connection-level failure falls back to the error partial; a per-action @@ -135,6 +155,10 @@ def _render_detail(request, connection, schema, table, error=None, notice=None, "preview_rows": rows, "preview": preview, "page_sizes": PAGE_SIZES, + # いま「画面に合わせる」で見ているか(選択欄の既定を出し分ける)。 + "fit_mode": request.GET.get("rows", DEFAULT_PAGE_SIZE) == "fit", + # ページ送りのリンクが持ち回る指定。fit なら fit のまま送る。 + "mode": "fit" if request.GET.get("rows", DEFAULT_PAGE_SIZE) == "fit" else preview.page_size, # Display-only: prefilled into the SQL editor as a starting point, # never executed server-side. The run path (run_query) is read-only # enforced by the DB. nosec B608 — not a query we build and execute. diff --git a/locale/ja/LC_MESSAGES/django.mo b/locale/ja/LC_MESSAGES/django.mo index 19968733e967e7624cb921d902fada3ecedafbd1..f989f10e855bba8608f7d290f08e4394d85b40db 100644 GIT binary patch delta 12810 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 Se0(Qq(VYp&TlYVi7WjW3oeN$7 delta 12735 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 LZ7T3rdc^+$I$Y~r diff --git a/locale/ja/LC_MESSAGES/django.po b/locale/ja/LC_MESSAGES/django.po index 559593d..7630f94 100644 --- a/locale/ja/LC_MESSAGES/django.po +++ b/locale/ja/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-04 13:53+0000\n" +"POT-Creation-Date: 2026-09-05 03:26+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -93,7 +93,7 @@ msgid "Unsupported index method: %(method)s" msgstr "未対応のインデックス方式: %(method)s" #: core/engines/mysql.py:757 core/engines/postgres.py:178 -#: core/views/tables.py:223 +#: core/views/tables.py:247 msgid "Select at least one column to index." msgstr "インデックス対象の列を1つ以上選んでください。" @@ -316,24 +316,24 @@ msgstr "エクスポートする結果がありません — 先にクエリを msgid "No table specified." msgstr "テーブルが指定されていません。" -#: core/views/tables.py:183 +#: core/views/tables.py:207 msgid "Choose a CSV file to import." msgstr "インポートする CSV ファイルを選んでください。" -#: core/views/tables.py:191 +#: core/views/tables.py:215 #, python-format msgid "Imported %(n)s row(s) from %(name)s." msgstr "%(name)s から %(n)s 行をインポートしました。" -#: core/views/tables.py:257 +#: core/views/tables.py:281 msgid "Enter a new table name." msgstr "新しいテーブル名を入力してください。" -#: core/views/tables.py:308 +#: core/views/tables.py:332 msgid "Enter a column name." msgstr "列名を入力してください。" -#: core/views/tables.py:328 +#: core/views/tables.py:352 msgid "Enter a new column name." msgstr "新しい列名を入力してください。" @@ -420,7 +420,7 @@ msgid "— click columns in order" msgstr "— 列を順にクリック" #: planner_lab/templates/partials/index_lab.html:64 -#: templates/partials/detail.html:160 templates/partials/detail.html:499 +#: templates/partials/detail.html:165 templates/partials/detail.html:543 msgid "unique" msgstr "ユニーク" @@ -557,7 +557,7 @@ msgstr "" "スケールシミュレーションには superuser 接続が必要です(pg_class を編集するた" "め)。" -#: templates/base.html:155 +#: templates/base.html:246 msgid "CLI database ops, as buttons." msgstr "CLI のDB操作を、ボタンで。" @@ -569,15 +569,15 @@ msgstr "新しい接続" msgid "Defaults point at the bundled sample DB." msgstr "初期値は同梱のサンプルDBを指しています。" -#: templates/index.html:16 templates/partials/detail.html:79 -#: templates/partials/detail.html:381 templates/partials/detail.html:414 +#: templates/index.html:16 templates/partials/detail.html:84 +#: templates/partials/detail.html:425 templates/partials/detail.html:458 #: templates/partials/replication.html:172 msgid "Type" msgstr "種別" -#: templates/index.html:17 templates/partials/detail.html:78 -#: templates/partials/detail.html:147 templates/partials/detail.html:377 -#: templates/partials/detail.html:490 templates/partials/extensions.html:28 +#: templates/index.html:17 templates/partials/detail.html:83 +#: templates/partials/detail.html:152 templates/partials/detail.html:421 +#: templates/partials/detail.html:534 templates/partials/extensions.html:28 #: templates/partials/extensions.html:64 templates/partials/objects.html:34 #: templates/partials/objects.html:95 templates/partials/objects.html:184 #: templates/partials/objects.html:252 templates/partials/objects.html:263 @@ -586,8 +586,8 @@ msgstr "種別" msgid "Name" msgstr "名前" -#: templates/index.html:17 templates/partials/detail.html:387 -#: templates/partials/detail.html:490 templates/partials/objects.html:188 +#: templates/index.html:17 templates/partials/detail.html:431 +#: templates/partials/detail.html:534 templates/partials/objects.html:188 #: templates/partials/objects.html:283 templates/partials/objects.html:310 msgid "(optional)" msgstr "(任意)" @@ -889,7 +889,7 @@ msgstr "対象" msgid "Source DB" msgstr "元DB" -#: templates/partials/backups.html:17 templates/partials/detail.html:150 +#: templates/partials/backups.html:17 templates/partials/detail.html:155 #: templates/partials/health.html:69 templates/partials/health.html:169 #: templates/partials/health.html:270 templates/partials/health.html:423 #: templates/partials/objects.html:37 @@ -1018,8 +1018,8 @@ msgstr "子(参照する側)" msgid "Parent (referenced)" msgstr "親(参照される側)" -#: templates/partials/dependencies.html:88 templates/partials/detail.html:62 -#: templates/partials/detail.html:148 templates/partials/health.html:226 +#: templates/partials/dependencies.html:88 templates/partials/detail.html:67 +#: templates/partials/detail.html:153 templates/partials/health.html:226 #: templates/partials/health.html:316 msgid "Columns" msgstr "列" @@ -1028,93 +1028,93 @@ msgstr "列" msgid "Constraint" msgstr "制約" -#: templates/partials/detail.html:29 +#: templates/partials/detail.html:34 msgid "Open in SQL" msgstr "SQL で開く" -#: templates/partials/detail.html:33 +#: templates/partials/detail.html:38 msgid "backup" msgstr "バックアップ" -#: templates/partials/detail.html:35 +#: templates/partials/detail.html:40 msgid "pg_dump this table as plain SQL" msgstr "このテーブルを平文SQLで pg_dump" -#: templates/partials/detail.html:38 +#: templates/partials/detail.html:43 msgid "pg_dump this table in custom format" msgstr "このテーブルをカスタム形式で pg_dump" -#: templates/partials/detail.html:46 +#: templates/partials/detail.html:51 msgid "export" msgstr "エクスポート" -#: templates/partials/detail.html:48 +#: templates/partials/detail.html:53 msgid "Download every row as CSV" msgstr "全行を CSV でダウンロード" -#: templates/partials/detail.html:50 +#: templates/partials/detail.html:55 msgid "Download every row as JSON" msgstr "全行を JSON でダウンロード" -#: templates/partials/detail.html:53 +#: templates/partials/detail.html:58 msgid "Append rows from a CSV file" msgstr "CSV ファイルから行を追加" -#: templates/partials/detail.html:53 +#: templates/partials/detail.html:58 msgid "import" msgstr "インポート" -#: templates/partials/detail.html:54 templates/partials/objects.html:65 +#: templates/partials/detail.html:59 templates/partials/objects.html:65 msgid "rename" msgstr "リネーム" -#: templates/partials/detail.html:63 templates/partials/health.html:25 +#: templates/partials/detail.html:68 templates/partials/health.html:25 msgid "Indexes" msgstr "インデックス" -#: templates/partials/detail.html:64 +#: templates/partials/detail.html:69 msgid "Data" msgstr "データ" -#: templates/partials/detail.html:66 +#: templates/partials/detail.html:71 msgid "Danger" msgstr "危険" -#: templates/partials/detail.html:72 templates/partials/detail.html:391 +#: templates/partials/detail.html:77 templates/partials/detail.html:435 msgid "+ Add column" msgstr "+ 列を追加" -#: templates/partials/detail.html:80 templates/partials/health.html:114 +#: templates/partials/detail.html:85 templates/partials/health.html:114 msgid "Nullable" msgstr "NULL 許容" -#: templates/partials/detail.html:81 templates/partials/detail.html:387 -#: templates/partials/detail.html:458 +#: templates/partials/detail.html:86 templates/partials/detail.html:431 +#: templates/partials/detail.html:502 msgid "Default" msgstr "デフォルト" -#: templates/partials/detail.html:93 +#: templates/partials/detail.html:98 msgid "yes" msgstr "はい" -#: templates/partials/detail.html:93 +#: templates/partials/detail.html:98 msgid "no" msgstr "いいえ" -#: templates/partials/detail.html:96 +#: templates/partials/detail.html:101 msgid "generated" msgstr "生成列" -#: templates/partials/detail.html:108 +#: templates/partials/detail.html:113 msgid "shape" msgstr "構造" -#: templates/partials/detail.html:111 templates/partials/objects.html:108 +#: templates/partials/detail.html:116 templates/partials/objects.html:108 #: templates/partials/objects.html:152 msgid "edit" msgstr "編集" -#: templates/partials/detail.html:113 +#: templates/partials/detail.html:118 #, python-format msgid "" "Drop column "%(col)s" from %(schema)s.%(table)s? This permanently " @@ -1123,116 +1123,122 @@ msgstr "" "%(schema)s.%(table)s から列 "%(col)s" を削除しますか?データは完全" "に削除されます。" -#: templates/partials/detail.html:117 templates/partials/detail.html:175 +#: templates/partials/detail.html:122 templates/partials/detail.html:180 #: templates/partials/objects.html:69 templates/partials/objects.html:112 #: templates/partials/objects.html:156 templates/partials/replication.html:198 msgid "drop" msgstr "削除" -#: templates/partials/detail.html:127 +#: templates/partials/detail.html:132 msgid "Sampling…" msgstr "サンプリング中…" -#: templates/partials/detail.html:137 +#: templates/partials/detail.html:142 msgid "Try an index and measure it before creating it for real" msgstr "本番作成の前にインデックスを試して計測します" -#: templates/partials/detail.html:138 +#: templates/partials/detail.html:143 msgid "Try an index" msgstr "インデックスを試す" -#: templates/partials/detail.html:140 templates/partials/detail.html:500 +#: templates/partials/detail.html:145 templates/partials/detail.html:544 msgid "+ Create index" msgstr "+ インデックスを作成" -#: templates/partials/detail.html:149 templates/partials/detail.html:494 +#: templates/partials/detail.html:154 templates/partials/detail.html:538 msgid "Method" msgstr "方式" -#: templates/partials/detail.html:159 templates/partials/replication.html:26 +#: templates/partials/detail.html:164 templates/partials/replication.html:26 msgid "primary" msgstr "主キー" -#: templates/partials/detail.html:161 templates/partials/health.html:187 +#: templates/partials/detail.html:166 templates/partials/health.html:187 msgid "A concurrent build failed — this index is unusable; drop it." msgstr "並行ビルドに失敗 — このインデックスは使用不可です。削除してください。" -#: templates/partials/detail.html:161 +#: templates/partials/detail.html:166 msgid "invalid" msgstr "無効" -#: templates/partials/detail.html:168 +#: templates/partials/detail.html:173 msgid "pk" msgstr "pk" -#: templates/partials/detail.html:171 +#: templates/partials/detail.html:176 #, python-format msgid "Drop index "%(name)s"?" msgstr "インデックス "%(name)s" を削除しますか?" -#: templates/partials/detail.html:184 +#: templates/partials/detail.html:189 msgid "No indexes on this table yet." msgstr "このテーブルにはまだインデックスがありません。" -#: templates/partials/detail.html:199 +#: templates/partials/detail.html:203 +#, fuzzy +#| msgid "Filter error" +msgid "Filter" +msgstr "フィルタエラー" + +#: templates/partials/detail.html:218 msgid "column…" msgstr "列…" -#: templates/partials/detail.html:209 +#: templates/partials/detail.html:228 msgid "contains" msgstr "を含む" -#: templates/partials/detail.html:210 +#: templates/partials/detail.html:229 msgid "starts with" msgstr "で始まる" -#: templates/partials/detail.html:211 +#: templates/partials/detail.html:230 msgid "is null" msgstr "が NULL" -#: templates/partials/detail.html:212 +#: templates/partials/detail.html:231 msgid "is not null" msgstr "が NULL でない" -#: templates/partials/detail.html:215 +#: templates/partials/detail.html:234 msgid "value" msgstr "値" -#: templates/partials/detail.html:217 +#: templates/partials/detail.html:236 msgid "Remove condition" msgstr "条件を削除" -#: templates/partials/detail.html:221 +#: templates/partials/detail.html:240 msgid "+ Condition" msgstr "+ 条件" -#: templates/partials/detail.html:222 +#: templates/partials/detail.html:241 msgid "Apply filters" msgstr "フィルタを適用" -#: templates/partials/detail.html:223 +#: templates/partials/detail.html:242 msgid "All conditions are ANDed · read-only" msgstr "全条件は AND 結合・読み取り専用" -#: templates/partials/detail.html:232 +#: templates/partials/detail.html:262 #, fuzzy, python-format #| msgid "rows %(n)s" msgid "rows %(a)s–%(b)s" msgstr "行 %(n)s" -#: templates/partials/detail.html:234 +#: templates/partials/detail.html:264 #, fuzzy, python-format #| msgid "Rows: about %(n)s (estimated from statistics, last analyzed %(d)s)" msgid "of about %(n)s (estimated from statistics)" msgstr "行数: 約 %(n)s(統計からの推定・最終 ANALYZE %(d)s)" -#: templates/partials/detail.html:236 +#: templates/partials/detail.html:266 #, fuzzy #| msgid "Rows: unknown (this table has never been analyzed)" msgid "of an unknown total — this table has never been analyzed" msgstr "行数: 不明(このテーブルはまだ ANALYZE されていません)" -#: templates/partials/detail.html:241 +#: templates/partials/detail.html:271 msgid "" "A table with no primary key has no defined row order, so paging can repeat " "or skip rows." @@ -1240,48 +1246,52 @@ msgstr "" "主キーの無いテーブルには決まった行の順序がないので、ページをめくると同じ行が" "二度出たり、どこにも出ない行ができることがあります。" -#: templates/partials/detail.html:242 +#: templates/partials/detail.html:272 msgid "⚠ No primary key — row order is not guaranteed between pages" msgstr "⚠ 主キーが無いため、ページ間で行の順序は保証されません" -#: templates/partials/detail.html:249 +#: templates/partials/detail.html:279 msgid "← Prev" msgstr "← 前" -#: templates/partials/detail.html:253 +#: templates/partials/detail.html:283 #, python-format msgid "page %(p)s of about %(t)s" msgstr "%(p)s / 約 %(t)s ページ" -#: templates/partials/detail.html:255 +#: templates/partials/detail.html:285 #, python-format msgid "page %(p)s" msgstr "%(p)s ページ" -#: templates/partials/detail.html:260 +#: templates/partials/detail.html:290 msgid "Next →" msgstr "次 →" -#: templates/partials/detail.html:266 +#: templates/partials/detail.html:304 +msgid "fit to window" +msgstr "画面に合わせる" + +#: templates/partials/detail.html:306 #, fuzzy, python-format #| msgid "%(n)s row" #| msgid_plural "%(n)s rows" msgid "%(n)s rows" msgstr "%(n)s 行" -#: templates/partials/detail.html:296 +#: templates/partials/detail.html:340 msgid "This table is empty." msgstr "このテーブルは空です。" -#: templates/partials/detail.html:307 +#: templates/partials/detail.html:351 msgid "Truncate table" msgstr "テーブルを truncate" -#: templates/partials/detail.html:308 +#: templates/partials/detail.html:352 msgid "Delete every row. A safety snapshot is taken first." msgstr "全行を削除します。先に安全スナップショットを取得します。" -#: templates/partials/detail.html:311 +#: templates/partials/detail.html:355 #, python-format msgid "" "Truncate "%(schema)s.%(table)s"? This permanently deletes every " @@ -1290,81 +1300,81 @@ msgstr "" ""%(schema)s.%(table)s" を truncate しますか?全行が完全に削除されま" "す。" -#: templates/partials/detail.html:311 templates/partials/detail.html:323 +#: templates/partials/detail.html:355 templates/partials/detail.html:367 #: templates/partials/objects.html:67 templates/partials/objects.html:110 #: templates/partials/objects.html:154 msgid "This runs:" msgstr "実行される SQL:" -#: templates/partials/detail.html:311 templates/partials/detail.html:323 +#: templates/partials/detail.html:355 templates/partials/detail.html:367 msgid "Rows: unknown (this table has never been analyzed)" msgstr "行数: 不明(このテーブルはまだ ANALYZE されていません)" -#: templates/partials/detail.html:311 templates/partials/detail.html:323 +#: templates/partials/detail.html:355 templates/partials/detail.html:367 #, python-format msgid "Rows: about %(n)s (estimated from statistics, last analyzed %(d)s)" msgstr "行数: 約 %(n)s(統計からの推定・最終 ANALYZE %(d)s)" -#: templates/partials/detail.html:311 templates/partials/detail.html:323 +#: templates/partials/detail.html:355 templates/partials/detail.html:367 msgid "Referenced by:" msgstr "参照しているテーブル:" -#: templates/partials/detail.html:311 templates/partials/detail.html:323 +#: templates/partials/detail.html:355 templates/partials/detail.html:367 msgid "A DROP … CASCADE would also drop:" msgstr "DROP … CASCADE で一緒に消えるもの:" -#: templates/partials/detail.html:314 +#: templates/partials/detail.html:358 msgid "Truncate" msgstr "Truncate" -#: templates/partials/detail.html:319 templates/partials/detail.html:326 +#: templates/partials/detail.html:363 templates/partials/detail.html:370 msgid "Drop table" msgstr "テーブルを削除" -#: templates/partials/detail.html:320 +#: templates/partials/detail.html:364 msgid "Remove the table and all its data. A safety snapshot is taken first." msgstr "テーブルと全データを削除します。先に安全スナップショットを取得します。" -#: templates/partials/detail.html:323 +#: templates/partials/detail.html:367 #, python-format msgid "Drop table "%(schema)s.%(table)s"? This cannot be undone." msgstr "" "テーブル "%(schema)s.%(table)s" を削除しますか?元に戻せません。" -#: templates/partials/detail.html:337 +#: templates/partials/detail.html:381 msgid "Rename table" msgstr "テーブルをリネーム" -#: templates/partials/detail.html:337 +#: templates/partials/detail.html:381 msgid "Add column" msgstr "列を追加" -#: templates/partials/detail.html:338 +#: templates/partials/detail.html:382 msgid "Edit column" msgstr "列を編集" -#: templates/partials/detail.html:338 +#: templates/partials/detail.html:382 msgid "Create index" msgstr "インデックスを作成" -#: templates/partials/detail.html:339 templates/partials/detail.html:367 +#: templates/partials/detail.html:383 templates/partials/detail.html:411 msgid "Import CSV" msgstr "CSV インポート" -#: templates/partials/detail.html:350 templates/partials/objects.html:201 +#: templates/partials/detail.html:394 templates/partials/objects.html:201 msgid "New name" msgstr "新しい名前" -#: templates/partials/detail.html:352 templates/partials/detail.html:405 +#: templates/partials/detail.html:396 templates/partials/detail.html:449 #: templates/partials/objects.html:203 msgid "Rename" msgstr "リネーム" -#: templates/partials/detail.html:361 +#: templates/partials/detail.html:405 msgid "CSV file" msgstr "CSV ファイル" -#: templates/partials/detail.html:365 +#: templates/partials/detail.html:409 msgid "" "The first row must be a header; columns are matched to the table by name. " "Rows are appended in one transaction — if any row's types don't fit, the " @@ -1374,43 +1384,43 @@ msgstr "" "は1つのトランザクションで追加され、いずれかの行の型が合わない場合はインポート" "全体がロールバックされます。事前に安全のためのスナップショットを取得します。" -#: templates/partials/detail.html:378 +#: templates/partials/detail.html:422 msgid "column name" msgstr "列名" -#: templates/partials/detail.html:390 +#: templates/partials/detail.html:434 msgid "not null" msgstr "not null" -#: templates/partials/detail.html:402 +#: templates/partials/detail.html:446 msgid "Rename to" msgstr "リネーム先" -#: templates/partials/detail.html:417 +#: templates/partials/detail.html:461 msgid "(current)" msgstr "(現在)" -#: templates/partials/detail.html:420 +#: templates/partials/detail.html:464 msgid "Change" msgstr "変更" -#: templates/partials/detail.html:425 +#: templates/partials/detail.html:469 msgid "Nullability" msgstr "NULL許可" -#: templates/partials/detail.html:433 +#: templates/partials/detail.html:477 msgid "set NOT NULL" msgstr "NOT NULL を設定" -#: templates/partials/detail.html:433 +#: templates/partials/detail.html:477 msgid "drop NOT NULL" msgstr "NOT NULL を解除" -#: templates/partials/detail.html:444 +#: templates/partials/detail.html:488 msgid "Rehearse it" msgstr "リハーサル" -#: templates/partials/detail.html:449 +#: templates/partials/detail.html:493 msgid "" "Runs the real statement and rolls it back. It takes a real ACCESS EXCLUSIVE " "lock while it runs, so readers queue behind it — briefly, but for real." @@ -1418,23 +1428,23 @@ msgstr "" "本物の文を実行して、ロールバックする。実行中は本物の ACCESS EXCLUSIVE ロック" "を取るので、読み取りはその後ろに並ぶ。短いが、本物。" -#: templates/partials/detail.html:458 +#: templates/partials/detail.html:502 msgid "(empty drops it)" msgstr "(空で解除)" -#: templates/partials/detail.html:461 templates/partials/settings_row.html:42 +#: templates/partials/detail.html:505 templates/partials/settings_row.html:42 msgid "Set" msgstr "設定" -#: templates/partials/detail.html:477 +#: templates/partials/detail.html:521 msgid "Click columns in the order you want them indexed." msgstr "インデックスしたい順に列をクリックしてください。" -#: templates/partials/detail.html:491 +#: templates/partials/detail.html:535 msgid "index name" msgstr "インデックス名" -#: templates/partials/detail.html:501 +#: templates/partials/detail.html:545 msgid "built CONCURRENTLY" msgstr "CONCURRENTLY で作成" @@ -2977,16 +2987,16 @@ msgstr "" msgid "Filter tables…" msgstr "テーブルを絞り込み…" -#: templates/partials/table_list.html:17 +#: templates/partials/table_list.html:21 msgid "Statistics have never been collected for this table — run ANALYZE" msgstr "" "このテーブルの統計がまだ集められていません(ANALYZE を実行してください)" -#: templates/partials/table_list.html:17 +#: templates/partials/table_list.html:21 msgid "unknown" msgstr "不明" -#: templates/partials/table_list.html:23 +#: templates/partials/table_list.html:27 #, python-format msgid "%(counter)s table" msgid_plural "%(counter)s tables" diff --git a/templates/base.html b/templates/base.html index c842cc2..d73d583 100644 --- a/templates/base.html +++ b/templates/base.html @@ -121,6 +121,88 @@ // Filter builder (table detail → Data tab): dynamic column/operator/value // rows. A stable id keys the x-for so editing one row doesn't disturb the // others; null / not-null operators hide the value input. + // データのグリッドを、画面の下端まで伸ばす。 + // + // 固定値(max-h-[60vh] など)だと、上に何があるかで余ったり溢れたりする ── + // 絞り込みを開けば上が伸び、タブを変えれば別のものが載る。**自分の上端を + // 実測して残りを取る**のが唯一ずれない方法。 + // + // これが無いと、グリッドの下を見るのに「まずページを送ってからグリッドを + // 送る」二段階になる。スクロールバーは 1 本にする。 + // いまの画面に、行が何行入るか。 + // + // 1 行の高さは実測する(描画済みの行があればそれ、無ければ既定 33px)。 + // グリッドの上端から main の内側の下端までが使える高さで、そこから + // 見出し行を引く。**この数を LIMIT にそのまま使わない** ── サーバ側で + // 範囲に丸める(core/views/tables.py の FIT_MIN / FIT_MAX)。 + window.cli2uiFitRows = function (gridEl) { + const main = gridEl.closest("main"); + const limit = main + ? main.getBoundingClientRect().bottom + - parseFloat(getComputedStyle(main).paddingBottom || 0) + : window.innerHeight; + const top = gridEl.getBoundingClientRect().top; + const head = gridEl.querySelector("thead"); + const row = gridEl.querySelector("tbody tr"); + const rowH = row ? row.getBoundingClientRect().height : 33; + const headH = head ? head.getBoundingClientRect().height : 30; + return Math.max(1, Math.floor((limit - top - headH - 8) / rowH)); + }; + + // 「画面に入るぶんだけ」を、描画してから 1 回だけ直す。 + // + // 何行入るかはブラウザでしか分からないので、1 つ目のテーブルは既定の 40 行で + // 来る。それが入りきらない(または余る)なら、測り直して**一度だけ**取り直す。 + // 測った数は覚えておき、2 つ目以降のテーブルは最初から正しい行数で来る。 + Alpine.data("fitRows", (loaded, url) => ({ + init() { + requestAnimationFrame(() => { + const grid = this.$root.querySelector(".overflow-auto") + || document.querySelector("#filter-result .overflow-auto"); + if (!grid || !grid.offsetParent) return; + const want = window.cli2uiFitRows(grid); + window.__cli2uiFit = want; + // 1 行 2 行の差で取り直すと、開くたびに 2 回叩くことになる。 + if (Math.abs(want - loaded) <= 2) return; + htmx.ajax("GET", url + "&rows=fit&fit=" + want, + { target: "#detail", swap: "innerHTML" }); + }); + }, + })); + + Alpine.data("fillHeight", (gap = 8) => ({ + init() { + this._fit = () => { + // 非表示のあいだは測れない(タブが閉じていると top が 0 になる)。 + if (!this.$el.offsetParent) return; + const top = this.$el.getBoundingClientRect().top; + if (top <= 0) return; + // 画面の下端ではなく **main の内側の下端**まで。footer と main の + // padding を食うと、こんど main 側にスクロールバーが出る。 + const main = this.$el.closest("main"); + const limit = main + ? main.getBoundingClientRect().bottom + - parseFloat(getComputedStyle(main).paddingBottom || 0) + : window.innerHeight; + this.$el.style.maxHeight = Math.max(160, limit - top - gap) + "px"; + }; + this._defer = () => requestAnimationFrame(this._fit); + this._defer(); + // タブが切り替わって**初めて見えたとき**に測り直す。init の時点では + // まだ display:none で、そこで測ると必ず外す。 + this._io = new IntersectionObserver(() => this._defer()); + this._io.observe(this.$el); + // 上の高さが変わる場面 ── リサイズ、絞り込みの開閉、タブの切り替え。 + window.addEventListener("resize", this._defer); + document.addEventListener("click", this._defer); + }, + destroy() { + this._io && this._io.disconnect(); + window.removeEventListener("resize", this._defer); + document.removeEventListener("click", this._defer); + }, + })); + Alpine.data("filterBuilder", () => ({ // 既定は畳む。絞り込みは毎回使うものではないのに、常に開いていると // その高さぶんグリッドが下へ押し出される。開いた状態はテーブルを @@ -149,10 +231,14 @@ })(); - -
+
{% block topnav %}{% endblock %}
-
+
{% block content %}{% endblock %}
-