Mattias Bodlund 3 weeks ago
parent
commit
2d9e1b1c91
12 changed files with 79 additions and 44 deletions
  1. +15
    -15
      Gemfile.lock
  2. +6
    -0
      app/assets/stylesheets/application.css
  3. +6
    -0
      app/controllers/game_controller.rb
  4. +9
    -0
      app/helpers/game_helper.rb
  5. +7
    -0
      app/javascript/application.js
  6. +8
    -3
      app/javascript/chance_controller.js
  7. +1
    -2
      app/views/game/_facts_content.html.erb
  8. +2
    -1
      app/views/game/_language-menu.html.erb
  9. +1
    -1
      app/views/game/last_save.html.erb
  10. +1
    -1
      app/views/game/stage.html.erb
  11. +1
    -1
      app/views/game/start.turbo_stream.erb
  12. +22
    -20
      app/views/layouts/application.html.erb

+ 15
- 15
Gemfile.lock View File

@ -281,7 +281,7 @@ GEM
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.4.2) rake (13.4.2)
rbs (4.1.3)
rbs (4.2.0)
logger logger
prism (>= 1.6.0) prism (>= 1.6.0)
tsort tsort
@ -299,8 +299,8 @@ GEM
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.7.0) request_store (1.7.0)
rack (>= 1.4) rack (>= 1.4)
rubocop (1.89.0)
json (~> 2.3)
rubocop (1.90.0)
json (>= 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
parallel (>= 1.10) parallel (>= 1.10)
@ -342,11 +342,11 @@ GEM
stimulus-rails (1.3.4) stimulus-rails (1.3.4)
railties (>= 6.0.0) railties (>= 6.0.0)
thor (1.5.0) thor (1.5.0)
thruster (0.1.25)
thruster (0.1.25-aarch64-linux)
thruster (0.1.25-arm64-darwin)
thruster (0.1.25-x86_64-darwin)
thruster (0.1.25-x86_64-linux)
thruster (0.1.26)
thruster (0.1.26-aarch64-linux)
thruster (0.1.26-arm64-darwin)
thruster (0.1.26-x86_64-darwin)
thruster (0.1.26-x86_64-linux)
timeout (0.6.1) timeout (0.6.1)
tsort (0.2.0) tsort (0.2.0)
turbo-rails (2.0.23) turbo-rails (2.0.23)
@ -525,14 +525,14 @@ CHECKSUMS
railties (8.1.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487 railties (8.1.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701 rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rbs (4.1.3) sha256=0c4474a9751cdc14364bfad0b3e53678323bbdc2c31683b0445932867dbab8c4
rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674
rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469 rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
redis (6.0.0) sha256=de71c10edd106986b759ec7ecdd08b63b9c0ee7414a0d0c1da73d31ba2bccda6 redis (6.0.0) sha256=de71c10edd106986b759ec7ecdd08b63b9c0ee7414a0d0c1da73d31ba2bccda6
redis-client (0.30.1) sha256=5151bc5c7bbfe48623732cdae3b900d8a22dc691cc7cdfacfb351ac55116522d redis-client (0.30.1) sha256=5151bc5c7bbfe48623732cdae3b900d8a22dc691cc7cdfacfb351ac55116522d
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb
rubocop (1.89.0) sha256=4dee8e3ee9c45e474834efd9e8d6fd031e8331c8dacdff0de4ad65ae0a6faae7
rubocop (1.90.0) sha256=9eb4c065b5c5154e4ef554c547972f3905a9eb6b53e657e580b6796b54bf8242
rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
rubocop-performance (1.27.0) sha256=eeeb1374d062a368ee1c787b70eb0b0cc4b184cb1f8565f424760946146d61ce rubocop-performance (1.27.0) sha256=eeeb1374d062a368ee1c787b70eb0b0cc4b184cb1f8565f424760946146d61ce
rubocop-rails (2.37.0) sha256=6e1645add5060e0328f8ddda0d820f55697c591394398bf14bb9dccb62f14b7e rubocop-rails (2.37.0) sha256=6e1645add5060e0328f8ddda0d820f55697c591394398bf14bb9dccb62f14b7e
@ -544,11 +544,11 @@ CHECKSUMS
ssrf_filter (1.5.0) sha256=e03dcdb9d1730d7f6710532a606b3543df2a448a0293ce04a2d995523c5a97f6 ssrf_filter (1.5.0) sha256=e03dcdb9d1730d7f6710532a606b3543df2a448a0293ce04a2d995523c5a97f6
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
thruster (0.1.25) sha256=d97d704e91fa37d5858bc238dea6186f7310b633abfeea9f2ba814f252203716
thruster (0.1.25-aarch64-linux) sha256=495265b11fc3c3596ae0d70ed902b60db82dafb2a5b47a2c527d3176ee3cf311
thruster (0.1.25-arm64-darwin) sha256=20338360cce4035e787135ae154a10663f3d3d61b15bfa72df347fe9d982de57
thruster (0.1.25-x86_64-darwin) sha256=633d337baee05e8eacbc18e3986ea6bbf8ea237abd67f5e4351b8aa710623c5e
thruster (0.1.25-x86_64-linux) sha256=e5586003b34dc2f4200f8412101e906621e276bb294d41d1b63a3c41e348503d
thruster (0.1.26) sha256=6e45e807086b29d51404841bd1ad493b67cd95892fd65dc5afcdd32e82e94ce8
thruster (0.1.26-aarch64-linux) sha256=2171cb34928c0250830008f535c4ab2ee57846cc3f5d3e96c3475f7b3de7a541
thruster (0.1.26-arm64-darwin) sha256=40676164c433abf31313422305d9e9e9210cf941b1befad88e2428b3b8dcc36c
thruster (0.1.26-x86_64-darwin) sha256=c7f7f0cbefd8030ea03bf88f6a988f595a0618a35a1a0d358e729c6898eb5b6a
thruster (0.1.26-x86_64-linux) sha256=3117a6ee430663f845a0457699fe9a05232dcc6c396e2cc83504de5a223c60e8
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f


+ 6
- 0
app/assets/stylesheets/application.css View File

@ -109,6 +109,12 @@ h2@property --glow-deg {
background-color: #0058a3; background-color: #0058a3;
} }
/* The whole game lives in a single turbo-frame so the URL never changes.
`contents` keeps the frame out of the layout: <main> stays a flex child of <body>. */
turbo-frame#game {
display: contents;
}
body { body {
margin: 0; margin: 0;
display: flex; display: flex;


+ 6
- 0
app/controllers/game_controller.rb View File

@ -2,6 +2,12 @@ class GameController < ApplicationController
include QuizHelperMethods include QuizHelperMethods
skip_before_action :require_player!, only: [ :index, :start ] skip_before_action :require_player!, only: [ :index, :start ]
# The whole game lives in one turbo-frame declared in the application layout,
# so the URL never changes. Frame requests must render that layout too:
# turbo-rails otherwise swaps in its bare "turbo_rails/frame" layout, and the
# response comes back without the <turbo-frame id="game"> Turbo is looking for.
layout "application"
before_action :set_node, only: [ :stage, :answer, :stage_result ] before_action :set_node, only: [ :stage, :answer, :stage_result ]
helper_method :root_node, helper_method :root_node,


+ 9
- 0
app/helpers/game_helper.rb View File

@ -19,6 +19,15 @@ module GameHelper
end end
# Answers are shuffled so the good one isn't always in the same slot, but a
# "chance" answer is pinned to the bottom — it reads as the wildcard option
# and the stage-1 confirm dialog is anchored to it.
def shuffled_answers(node)
chance, rest = node.children.ordered.partition(&:chance?)
rest.shuffle + chance
end
def render_responsive_picture(asset = nil, alt: "", lazy: false, fetchpriority: nil) def render_responsive_picture(asset = nil, alt: "", lazy: false, fetchpriority: nil)
return unless asset return unless asset


+ 7
- 0
app/javascript/application.js View File

@ -17,6 +17,13 @@ application.register("chance", ChanceController)
application.register("share", ShareController) application.register("share", ShareController)
application.register("modal", ModalController) application.register("modal", ModalController)
// The whole game runs inside one turbo-frame so the URL never changes. Frame
// navigations don't reset scroll the way a full page visit does, so each new
// screen would otherwise open at the previous screen's scroll offset.
document.addEventListener("turbo:frame-render", (event) => {
if (event.target.id === "game") window.scrollTo(0, 0)
})
// Configure Stimulus development experience // Configure Stimulus development experience
application.debug = false application.debug = false
window.Stimulus = application window.Stimulus = application


+ 8
- 3
app/javascript/chance_controller.js View File

@ -4,6 +4,9 @@ export default class extends Controller {
static targets = ["dialog"] static targets = ["dialog"]
confirm(event) { confirm(event) {
// The second pass — after the player confirmed — falls through to Turbo.
if (this.confirmed) return
event.preventDefault() event.preventDefault()
this.pendingForm = event.target this.pendingForm = event.target
this.dialogTarget.showModal() this.dialogTarget.showModal()
@ -11,7 +14,10 @@ export default class extends Controller {
ok() { ok() {
this.dialogTarget.close() this.dialogTarget.close()
this.pendingForm?.submit()
// requestSubmit(), not submit(): the native call skips the submit event, so
// Turbo would miss it and navigate the whole page out of the game frame.
this.confirmed = true
this.pendingForm?.requestSubmit()
} }
cancel() { cancel() {
@ -19,8 +25,7 @@ export default class extends Controller {
this.pendingForm = null this.pendingForm = null
} }
backdropClick(event) {
console.info(event.target === this.dialogTarget)
backdropClick(event) {
if (event.target === this.dialogTarget) this.cancel() if (event.target === this.dialogTarget) this.cancel()
} }
} }

+ 1
- 2
app/views/game/_facts_content.html.erb View File

@ -27,7 +27,6 @@
</div> </div>
<article> <article>
<%= link_to tag.span(t("game.got_it_lets_get_started")), {action: "intro"}, class: "cta",
data: { turbo_frame: "_top" } %>
<%= link_to tag.span(t("game.got_it_lets_get_started")), {action: "intro"}, class: "cta" %>
</article> </article>
</div> </div>

+ 2
- 1
app/views/game/_language-menu.html.erb View File

@ -33,7 +33,8 @@
<li><%= link_to v, <li><%= link_to v,
url_for(locale: k), url_for(locale: k),
class: ("current" if current), class: ("current" if current),
"aria-current": ("page" if current) %></li>
"aria-current": ("page" if current),
data: { turbo_frame: "_top" } %></li>
<% end%> <% end%>
</ul> </ul>
</nav> </nav>

+ 1
- 1
app/views/game/last_save.html.erb View File

@ -20,7 +20,7 @@
<article class="answers-container" data-controller="chance"> <article class="answers-container" data-controller="chance">
<% @node.children.ordered.each do |node| %>
<% shuffled_answers(@node).each do |node| %>
<%= button_to node.title, url_for(action: "answer_last_save", value: node.id) %> <%= button_to node.title, url_for(action: "answer_last_save", value: node.id) %>
<% end %> <% end %>


+ 1
- 1
app/views/game/stage.html.erb View File

@ -17,7 +17,7 @@
</article> </article>
<article class="answers-container" data-controller="chance"> <article class="answers-container" data-controller="chance">
<% @node.children.ordered.each do |node| %>
<% shuffled_answers(@node).each do |node| %>
<%= button_to node.title, url_for(action: "answer", value: node.id), <%= button_to node.title, url_for(action: "answer", value: node.id),
class: ("chance" if node.chance?), class: ("chance" if node.chance?),
form: ((stage_index == 1 and node.chance?) ? { data: { action: "submit->chance#confirm" } } : {}) %> form: ((stage_index == 1 and node.chance?) ? { data: { action: "submit->chance#confirm" } } : {}) %>


+ 1
- 1
app/views/game/start.turbo_stream.erb View File

@ -1,4 +1,4 @@
<%= turbo_stream.append "page" do %>
<%= turbo_stream.append "game" do %>
<dialog class="facts-modal" <dialog class="facts-modal"
data-controller="modal" data-controller="modal"
data-modal-target="dialog" data-modal-target="dialog"


+ 22
- 20
app/views/layouts/application.html.erb View File

@ -20,28 +20,30 @@
<%= frontend_javascript_importmap_tags %w[application @hotwired/turbo-rails @hotwired/stimulus language_menu_controller carousel_controller intro_controller chance_controller share_controller modal_controller] %> <%= frontend_javascript_importmap_tags %w[application @hotwired/turbo-rails @hotwired/stimulus language_menu_controller carousel_controller intro_controller chance_controller share_controller modal_controller] %>
</head> </head>
<body id="page"> <body id="page">
<%= content_for :header %>
<%= turbo_frame_tag "game" do %>
<%= content_for :header %>
<% if respond_to?(:current_player) && current_player %>
<aside class="debug-score">
<details open>
<summary>Debug score (<%= current_player.score %>)</summary>
<dl>
<dt>Overall</dt><dd><%= current_player.score %></dd>
<dt>Food waste</dt><dd><%= current_player.score_for(:food_waste) %></dd>
<dt>Emissions</dt><dd><%= current_player.score_for(:emissions) %></dd>
<dt>Income</dt><dd><%= current_player.score_for(:income) %></dd>
<dt>Done</dt><dd><%= current_player.is_done %></dd>
</dl>
</details>
</aside>
<% end %>
<% if respond_to?(:current_player) && current_player %>
<aside class="debug-score">
<details open>
<summary>Debug score (<%= current_player.score %>)</summary>
<dl>
<dt>Overall</dt><dd><%= current_player.score %></dd>
<dt>Food waste</dt><dd><%= current_player.score_for(:food_waste) %></dd>
<dt>Emissions</dt><dd><%= current_player.score_for(:emissions) %></dd>
<dt>Income</dt><dd><%= current_player.score_for(:income) %></dd>
<dt>Done</dt><dd><%= current_player.is_done %></dd>
</dl>
</details>
</aside>
<% end %>
<%= tag.main class: [@result_node, @node].compact.map { |n| n.template }.first,
data: {
controller: content_for(:main_controller)
} do %>
<%= yield %>
<%= tag.main class: [@result_node, @node].compact.map { |n| n.template }.first,
data: {
controller: content_for(:main_controller)
} do %>
<%= yield %>
<% end %>
<% end %> <% end %>
</body> </body>


Loading…
Cancel
Save