From 5bd1e821f75cb740829c1fded8f57477a39d0d72 Mon Sep 17 00:00:00 2001 From: Charlotte Van Petegem Date: Tue, 20 May 2025 18:28:20 +0200 Subject: [PATCH] Finish remaining question types --- .../stylesheets/application.postcss.css | 35 +++ app/controllers/questions_controller.rb | 12 ++ app/controllers/sections_controller.rb | 1 + app/javascript/acrostic_answer.js | 39 ++++ app/javascript/application.js | 10 + app/javascript/connections_answer.js | 19 ++ app/javascript/lyrics_answer.js | 21 ++ app/javascript/politicians_answer.js | 5 +- app/models/question.rb | 4 +- app/views/questions/_acrostic_form.html.erb | 22 ++ app/views/questions/_acrostic_input.html.erb | 6 + app/views/questions/_acrostic_show.html.erb | 3 + .../questions/_connections_form.html.erb | 11 + app/views/questions/_lyrics_form.html.erb | 15 ++ .../questions/_lyrics_given_part.html.erb | 3 + app/views/questions/_lyrics_input.html.erb | 6 + .../questions/_politicians_form.html.erb | 4 +- app/views/sections/show.html.erb | 5 + test/fixtures/questions.yml | 202 ++++++++++++++++++ test/fixtures/sections.yml | 16 ++ 20 files changed, 431 insertions(+), 8 deletions(-) create mode 100644 app/javascript/acrostic_answer.js create mode 100644 app/javascript/connections_answer.js create mode 100644 app/javascript/lyrics_answer.js create mode 100644 app/views/questions/_acrostic_form.html.erb create mode 100644 app/views/questions/_acrostic_input.html.erb create mode 100644 app/views/questions/_acrostic_show.html.erb create mode 100644 app/views/questions/_connections_form.html.erb create mode 100644 app/views/questions/_lyrics_form.html.erb create mode 100644 app/views/questions/_lyrics_given_part.html.erb create mode 100644 app/views/questions/_lyrics_input.html.erb diff --git a/app/assets/stylesheets/application.postcss.css b/app/assets/stylesheets/application.postcss.css index c155eda..b1470bd 100644 --- a/app/assets/stylesheets/application.postcss.css +++ b/app/assets/stylesheets/application.postcss.css @@ -3,3 +3,38 @@ display: flex; flex-direction: row; } + +.acrostic-row { + display: flex; + flex-direction: row; +} + +.acrostic-offset,.acrostic-number { + display: inline-block; + width: 2rem; + height: 2rem; + border: 2px solid transparent; +} + +.acrostic-input { + padding: 0; + width: 2rem; + height: 2rem; + text-transform: uppercase; + text-align: center; +} + +.acrostic-column { + background-color: lightgrey; +} + +.connections-grid { + display: grid; + grid-template-columns: 25% 25% 25% 25%; +} + +.connections-element { + border: 1px solid black; + margin: 0.5em; + padding: 1em; +} diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index 4b56b49..0db46d4 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -24,4 +24,16 @@ class QuestionsController < ApplicationController def handle_politicians_answer @answer.update!(data: params[:order]) end + + def handle_acrostic_answer + @answer.update!(data: params[:data]) + end + + def handle_connections_answer + @answer.update!(data: params[:order]) + end + + def handle_lyrics_answer + @answer.update!(data: params[:data]) + end end diff --git a/app/controllers/sections_controller.rb b/app/controllers/sections_controller.rb index 7ad9a61..16edb04 100644 --- a/app/controllers/sections_controller.rb +++ b/app/controllers/sections_controller.rb @@ -4,6 +4,7 @@ class SectionsController < ApplicationController end def show + @sections = Section.all @section = Section.find(params[:id]) @questions = @section.questions.includes(:answer) end diff --git a/app/javascript/acrostic_answer.js b/app/javascript/acrostic_answer.js new file mode 100644 index 0000000..bee696f --- /dev/null +++ b/app/javascript/acrostic_answer.js @@ -0,0 +1,39 @@ +import { submitValueDebounced } from './submit_value.js' + +export default function initAcrosticQuestions() { + document.querySelectorAll('[data-behaviour="question_acrostic"]').forEach((acrostic) => { + const submitUrl = acrostic.dataset.submitUrl + const _submit = submitValueDebounced() + + const allInputs = Array.from(acrostic.querySelectorAll(".acrostic-input")) + + function submit() { + const data = allInputs.map((input) => input.value) + _submit(submitUrl, {data}) + } + + allInputs.forEach((input) => { + const index = Number.parseInt(input.dataset.index) + const previous = acrostic.querySelector(`.acrostic-input[data-index="${index - 1}"]`) + const next = acrostic.querySelector(`.acrostic-input[data-index="${index + 1}"]`) + input.addEventListener('input', (event) => { + if (event.data) { + input.value = event.data[0] + } + submit() + if (event.data) { + if (next) { + next.focus() + } + } else { + if (previous) { + previous.focus() + } + } + }) + input.addEventListener('focus', () => { + input.select() + }) + }) + }) +} diff --git a/app/javascript/application.js b/app/javascript/application.js index fe5d93e..391047e 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -2,9 +2,19 @@ import initSimpleQuestions from './simple_input.js' import initImageQuestions from './image_input.js' import initPoliticianQuestions from './politicians_answer.js' +import initAcrosticQuestions from './acrostic_answer.js' +import initConnectionsQuestions from './connections_answer.js' +import initLyricsQuestions from './lyrics_answer.js' + +import { Sortable, Swap } from 'sortablejs' + +Sortable.mount(new Swap()) addEventListener("DOMContentLoaded", () => { initSimpleQuestions() initImageQuestions() initPoliticianQuestions() + initAcrosticQuestions() + initConnectionsQuestions() + initLyricsQuestions() }) diff --git a/app/javascript/connections_answer.js b/app/javascript/connections_answer.js new file mode 100644 index 0000000..928692c --- /dev/null +++ b/app/javascript/connections_answer.js @@ -0,0 +1,19 @@ +import { Sortable } from 'sortablejs' + +import { submitValue } from './submit_value.js' + +export default function initConnectionsQuestions() { + + + document.querySelectorAll('[data-behaviour="connections_answer').forEach((list) => { + const submitUrl = list.dataset.submitUrl + + Sortable.create(list, { + swap: true, + onUpdate: (event) => { + const newOrder = Array.from(event.to.children).map((el) => Number.parseInt(el.dataset.id)) + submitValue(submitUrl, { order: newOrder }) + } + }) + }) +} diff --git a/app/javascript/lyrics_answer.js b/app/javascript/lyrics_answer.js new file mode 100644 index 0000000..00a0e08 --- /dev/null +++ b/app/javascript/lyrics_answer.js @@ -0,0 +1,21 @@ +import { submitValueDebounced } from './submit_value.js' + +export default function initLyricsQuestions() { + document.querySelectorAll('[data-behaviour="lyrics_answer"]').forEach((lyrics) => { + const submitUrl = lyrics.dataset.submitUrl + const _submit = submitValueDebounced() + + const allInputs = Array.from(lyrics.querySelectorAll(".lyrics-input")) + + function submit() { + const data = allInputs.map((input) => input.value) + _submit(submitUrl, {data}) + } + + allInputs.forEach((input) => { + const index = Number.parseInt(input.dataset.index) + input.addEventListener('input', () => submit()) + input.addEventListener('change', () => submit()) + }) + }) +} diff --git a/app/javascript/politicians_answer.js b/app/javascript/politicians_answer.js index f29e457..b9f0a6f 100644 --- a/app/javascript/politicians_answer.js +++ b/app/javascript/politicians_answer.js @@ -1,11 +1,8 @@ -import { Sortable, Swap } from 'sortablejs' +import { Sortable } from 'sortablejs' import { submitValue } from './submit_value.js' export default function initPoliticianQuestions() { - - Sortable.mount(new Swap()) - document.querySelectorAll('[data-behaviour="politicians_answer').forEach((list) => { const submitUrl = list.dataset.submitUrl diff --git a/app/models/question.rb b/app/models/question.rb index 265a6c5..437e387 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -21,8 +21,8 @@ # section_id (section_id => sections.id) # class Question < ApplicationRecord - enum :answer_kind, { simple: 0, image: 1, politicians: 2 }, prefix: true - enum :question_kind, { simple: 0, video: 1 }, prefix: true + enum :answer_kind, { simple: 0, image: 1, politicians: 2, acrostic: 3, connections: 4, lyrics: 5 }, prefix: true + enum :question_kind, { simple: 0, video: 1, acrostic: 3 }, prefix: true belongs_to :section has_one :answer diff --git a/app/views/questions/_acrostic_form.html.erb b/app/views/questions/_acrostic_form.html.erb new file mode 100644 index 0000000..421bdfc --- /dev/null +++ b/app/views/questions/_acrostic_form.html.erb @@ -0,0 +1,22 @@ +<% max_position = question.data.map { |c| c["position"] }.max %> +<% index = -1 %> +
+ <% question.data.each.with_index do |constraints, i| %> +
+ <%= i + 1 %> + <% (max_position - constraints["position"]).times do %> + + <% end %> + <% (constraints["position"] - 1).times do %> + <%= render partial: "questions/acrostic_input", locals: {on_column: false, index: (index += 1), answer_data: question.answer&.data} %> + <% end %> + <%= render partial: "questions/acrostic_input", locals: {on_column: true, index: (index += 1), answer_data: question.answer&.data} %> + <% (constraints["length"] - constraints["position"]).times do %> + <%= render partial: "questions/acrostic_input", locals: {on_column: false, index: (index += 1), answer_data: question.answer&.data} %> + <% end %> +
+ <% end %> +
diff --git a/app/views/questions/_acrostic_input.html.erb b/app/views/questions/_acrostic_input.html.erb new file mode 100644 index 0000000..dec4818 --- /dev/null +++ b/app/views/questions/_acrostic_input.html.erb @@ -0,0 +1,6 @@ +<%= tag.input class: {"acrostic-input": true, "acrostic-column": on_column}, + data: {index: index}, + value: answer_data&.then { |it| it[index] }, + type: "text", + autocomplete: "off" +%> diff --git a/app/views/questions/_acrostic_show.html.erb b/app/views/questions/_acrostic_show.html.erb new file mode 100644 index 0000000..c012945 --- /dev/null +++ b/app/views/questions/_acrostic_show.html.erb @@ -0,0 +1,3 @@ +<% question.text.lines.each.with_index do |q, i| %> +

<%= i + 1 %>. <%= q %>

+<% end %> diff --git a/app/views/questions/_connections_form.html.erb b/app/views/questions/_connections_form.html.erb new file mode 100644 index 0000000..1de2f2c --- /dev/null +++ b/app/views/questions/_connections_form.html.erb @@ -0,0 +1,11 @@ +
+ <% answers = question.data.each.with_index.to_a %> + <% answers = reorder_with_indices(answers, question.answer.data) if question.answer.present? %> + <% answers.each do |a, i| %> + <%= a %> + <% end %> +
diff --git a/app/views/questions/_lyrics_form.html.erb b/app/views/questions/_lyrics_form.html.erb new file mode 100644 index 0000000..0021f30 --- /dev/null +++ b/app/views/questions/_lyrics_form.html.erb @@ -0,0 +1,15 @@ +<% index = -1 %> +
+ <% question.data.lines.each do |l| %> + <% parts = l.split "{}", -1 %> +

+ <%= render partial: "questions/lyrics_given_part", locals: {text: parts[0]} %> + <% parts[1..].each do |text| %> + <%= render partial: "questions/lyrics_input", locals: {index: (index += 1), answer_data: question.answer&.data} %> + <%= render partial: "questions/lyrics_given_part", locals: {text: text} %> + <% end %> +

+ <% end %> +
diff --git a/app/views/questions/_lyrics_given_part.html.erb b/app/views/questions/_lyrics_given_part.html.erb new file mode 100644 index 0000000..49d1477 --- /dev/null +++ b/app/views/questions/_lyrics_given_part.html.erb @@ -0,0 +1,3 @@ +<% if text.present? %> + <%= text %> +<% end %> diff --git a/app/views/questions/_lyrics_input.html.erb b/app/views/questions/_lyrics_input.html.erb new file mode 100644 index 0000000..92a8fec --- /dev/null +++ b/app/views/questions/_lyrics_input.html.erb @@ -0,0 +1,6 @@ +<%= tag.input class: "lyrics-input", + data: {index: index}, + value: answer_data&.then { |it| it[index] }, + type: "text", + autocomplete: "off" +%> diff --git a/app/views/questions/_politicians_form.html.erb b/app/views/questions/_politicians_form.html.erb index 2e6c8c4..5f82e02 100644 --- a/app/views/questions/_politicians_form.html.erb +++ b/app/views/questions/_politicians_form.html.erb @@ -5,8 +5,8 @@ <% end %>
+ data-submit-url="<%= answer_question_url(question.id) %>" + > <% answers = question.data["right"].each.with_index.to_a %> <% answers = reorder_with_indices(answers, question.answer.data) if question.answer.present? %> <% answers.each do |a, i| %> diff --git a/app/views/sections/show.html.erb b/app/views/sections/show.html.erb index 107eed8..5735bb9 100644 --- a/app/views/sections/show.html.erb +++ b/app/views/sections/show.html.erb @@ -1,3 +1,8 @@ +<%= link_to sections_url do %>Home<% end %> +<% @sections.each do |section| %> + <%= link_to section.title, section_url(section) %> +<% end %> +

<%= @section.title %>

<%= @section.description %>

diff --git a/test/fixtures/questions.yml b/test/fixtures/questions.yml index 632ae52..de7fde0 100644 --- a/test/fixtures/questions.yml +++ b/test/fixtures/questions.yml @@ -421,3 +421,205 @@ photos_sint_kruis_winkel: text: De grens met Zelzate. answer_kind: <%= Question.answer_kinds[:image] %> question_kind: <%= Question.question_kinds[:simple] %> + +acrostic_acrostic: + section: acrostic + text: | + Wat is het oudste publiek toegankelijke museum van België? + Waar is Chokri Ben Chika tegen? + Waar kan je met je oldtimer binnen met een dagpas? + Wat is de bijnaam van de Gentenaar? + Wie kan je bewonderen in Poppentheater Pedrolino? + Welke dag gaan jullie het slechtst slapen? + Welke mini Gentse Feesten gaan door rond 15 augustus? + data: '<%= JSON.dump([ + { position: 3, length: 3 }, + { position: 5, length: 8 }, + { position: 3, length: 3 }, + { position: 7, length: 14 }, + { position: 4, length: 14 }, + { position: 3, length: 9 }, + { position: 11, length: 16 }, + ]) %>' + answer_kind: <%= Question.answer_kinds[:acrostic] %> + question_kind: <%= Question.question_kinds[:acrostic] %> + +connections_buildings: + section: connections + text: Gebouwen + data: '<%= + JSON.dump( + ["Huwelijk", "Burcht", "Middeleeuwen", "Nieuw slot", "UGent", "Wetenschap", "Museum", "Plantentuin", "Hotel", "Matexi", "Provincie", "Defensie"].shuffle + ) + %>' + answer_kind: <%= Question.answer_kinds[:connections] %> + question_kind: <%= Question.question_kinds[:simple] %> + +connections_food: + section: connections + text: Eten + data: '<%= + JSON.dump( + ["Bouillon", "Kip", "Kervel", "Brood", "Neus", "Kegel", "Framboos", "Kraam", "Kaneel", "Bruine suiker", "Hondsdolheid", "Sint-Hubertus"].shuffle + ) + %>' + answer_kind: <%= Question.answer_kinds[:connections] %> + question_kind: <%= Question.question_kinds[:simple] %> + +connections_sport: + section: connections + text: Sport + data: '<%= + JSON.dump( + ["Marathon", "Tokyo", "Somalië", "1989", "5K", "Roeien", "Nachez", "Blaarmeersen", "Voetbal", "Baro", "2013", "Arteveldestadion"].shuffle + ) + %>' + answer_kind: <%= Question.answer_kinds[:connections] %> + question_kind: <%= Question.question_kinds[:simple] %> + +translations_lousbirge: + section: translations + text: "we goan eu in Lousbirge steeke" + answer_kind: <%= Question.answer_kinds[:simple] %> + question_kind: <%= Question.question_kinds[:simple] %> + +translations_alvekoate: + section: translations + text: "da weete mijn kluuten uuk en 't zijn gîen alvekoate" + answer_kind: <%= Question.answer_kinds[:simple] %> + question_kind: <%= Question.question_kinds[:simple] %> + +translations_deursteeke: + section: translations + text: "de koarte deursteeke" + answer_kind: <%= Question.answer_kinds[:simple] %> + question_kind: <%= Question.question_kinds[:simple] %> + +translations_dampuurte: + section: translations + text: "'k goa eu mee eu muile teege de Dampuurte plakke, dadde viertien doage bruine ziepe schijt" + answer_kind: <%= Question.answer_kinds[:simple] %> + question_kind: <%= Question.question_kinds[:simple] %> + +translations_wielekes: + section: translations + text: "o mijn tante wielekes g'hat, 't was een kerre" + answer_kind: <%= Question.answer_kinds[:simple] %> + question_kind: <%= Question.question_kinds[:simple] %> + +translations_soepe: + section: translations + text: "veur em schept God den dag en moed'r schept de soepe" + answer_kind: <%= Question.answer_kinds[:simple] %> + question_kind: <%= Question.question_kinds[:simple] %> + +music_one: + section: music + text: "" + data: '<%= JSON.dump <' + answer_kind: <%= Question.answer_kinds[:lyrics] %> + question_kind: <%= Question.question_kinds[:simple] %> + +music_two: + section: music + text: "" + data: '<%= JSON.dump <' + answer_kind: <%= Question.answer_kinds[:lyrics] %> + question_kind: <%= Question.question_kinds[:simple] %> + +music_three: + section: music + text: "" + data: '<%= JSON.dump <' + answer_kind: <%= Question.answer_kinds[:lyrics] %> + question_kind: <%= Question.question_kinds[:simple] %> + +music_four: + section: music + text: "" + data: '<%= JSON.dump <' + answer_kind: <%= Question.answer_kinds[:lyrics] %> + question_kind: <%= Question.question_kinds[:simple] %> diff --git a/test/fixtures/sections.yml b/test/fixtures/sections.yml index b461a57..0715524 100644 --- a/test/fixtures/sections.yml +++ b/test/fixtures/sections.yml @@ -41,3 +41,19 @@ world: photos: title: Fotoronde description: In deze ronde moet je met elk van de vermelde dingen een foto nemen. + +acrostic: + title: Verborgen hint + description: Vul alle vragen juist in en vind de verborgen hint. Leestekens en spaties moet je weglaten. + +connections: + title: Steekronde + description: Verplaats de termen zodat de bijeenhorende begrippen op één rij staan. + +translations: + title: Gentse gezegden + description: Vertaal de volgende Gentse gezegden + +music: + title: Muziekronde + description: Vul de onderstaande liedjesteksten aan.