SandboxページをTurbo Frame化してプリセット切り替えを実装
LexxyのSandboxページが、Turbo Frameを使ったプリセット切り替え機能に刷新されました。これにより、ページ遷移なしでエディタの初期コンテンツを切り替えられるようになり、開発中の動作確認がより効率的になります。
背景
従来のSandboxページでは、異なる初期コンテンツ(デフォルト、テーブル、コードブロックなど)をロードするために、Stimulusコントローラがfetchでパーシャルを取得し、エディタの値を書き換える実装になっていました。この方式では、各プリセットへの直リンクが作成できず、ブラウザ履歴にも反映されないという制約がありました。
本PRは、プリセット切り替えをTurbo Frameベースに再実装し、#688で報告されている問題への暫定的な回避策を含んでいます。
技術的な変更
変更前:
async loadContent({ params: { partial } }) {
const response = await fetch(`/demo_contents/${partial}`)
const html = await response.text()
this.editorTarget.value = html.trim()
await new Promise(resolve => requestAnimationFrame(resolve))
this.refresh()
}
<div class="editor-value-buttons">
<button data-action="click->lexxy-output#loadContent" data-lexxy-output-partial-param="default">Default</button>
<button data-action="click->lexxy-output#loadContent" data-lexxy-output-partial-param="tables">Tables</button>
<!-- 他のボタン -->
</div>
変更後:
get "sandbox/:template", to: "sandbox#show"
ALLOWED_TEMPLATES = %w[default tables code lists highlights images empty]
def show
@template = if params[:template].presence_in ALLOWED_TEMPLATES
params[:template]
else
"default"
end
end
<main data-controller="lexxy-output history">
<section class="input">
<div class="editor-value-buttons">
<a href="/sandbox/default" class="button" data-turbo-frame="editor-frame">Default</a>
<a href="/sandbox/tables" class="button" data-turbo-frame="editor-frame">Tables</a>
<!-- 他のリンク -->
</div>
<turbo-frame id="editor-frame" src="/sandbox/<%= @template %>">
<%= render @template %>
</turbo-frame>
</section>
</main>
Stimulus Controller経由のfetch処理が削除され、代わりにルーティングとTurbo Frameによるプリセット切り替えに変更されています。コントローラではALLOWED_TEMPLATESのホワイトリストでセキュリティを確保し、不正なテンプレート名が指定された場合はデフォルトにフォールバックします。
Turbo Frame内での履歴管理
Turbo Frameによるナビゲーションでは、デフォルトではブラウザ履歴が更新されません。この問題に対処するため、履歴管理用のStimulus Controllerが追加されています。
import { Controller } from "@hotwired/stimulus"
export default class HistoryController extends Controller {
pushFromFrameLoad(event) {
const frame = event.target
const url = frame.src
window.history.pushState({}, "", url)
}
}
turbo:frame-loadイベントをフックして、フレームのURL変更時にhistory.pushState()で履歴エントリを追加しています。これにより、ブラウザの戻る/進むボタンでプリセット間を移動できるようになります。PR本文では、この実装が#688の解決までの「work-around」として位置づけられています。
プリセットの拡充
新たにimagesプリセットが追加され、画像の表示パターン(単一画像、ギャラリー、連続配置)をテストできるようになりました。
<h3>Single image</h3>
<img src="<%= asset_path('shape-2.svg') %>" alt="Wave pattern with gradient">
<p>Hello world</p>
<h3>Image gallery</h3>
<div>
<img src="<%= asset_path('shape-1.svg') %>" alt="Geometric pattern with circles">
<img src="<%= asset_path('shape-2.svg') %>" alt="Wave pattern with gradient">
<img src="<%= asset_path('shape-3.svg') %>" alt="Rotated squares pattern">
</div>
<h3>Consecutive images</h3>
<img src="<%= asset_path('shape-1.svg') %>" alt="Geometric pattern with circles">
<img src="<%= asset_path('shape-2.svg') %>" alt="Wave pattern with gradient">
<img src="<%= asset_path('shape-3.svg') %>" alt="Rotated squares pattern">
3つのSVG画像ファイル(shape-1.svg、shape-2.svg、shape-3.svg)がtest/dummy/app/assets/images/に追加され、グラデーションや幾何学模様を含むテスト用アセットとして利用できます。
設計判断
Stimulusベースの動的ロードからルーティングベースの実装への移行は、URLの直接共有可能性とブラウザネイティブなナビゲーション体験を優先した判断です。各プリセットが独立したURLを持つことで、特定の状態を再現しやすくなり、開発時の動作確認効率が向上します。
一方で、Turbo Frame内での履歴管理の実装は、PR本文で明示的に「work-around」として扱われています。history_controller.jsの追加は、#688が解決されるまでの暫定的な対応として機能します。
まとめ
本PRは、Sandboxページの実装をTurbo Frameベースに再構築し、プリセット切り替えをURLルーティングで実現しています。Stimulusによる動的コンテンツロードを排除することで、各プリセットへの直リンクとブラウザ履歴の統合を可能にしました。開発体験の向上を実現しつつ、既知の制約への実用的な回避策を提供しています。