diff --git a/Gemfile.lock b/Gemfile.lock index 62376a9..db50a3b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -154,7 +154,7 @@ GEM kaminari-core (= 1.2.2) kaminari-core (1.2.2) language_server-protocol (3.17.0.5) - lexxy (0.8.5.beta) + lexxy (0.9.0.beta) rails (>= 8.0.2) lint_roller (1.1.0) logger (1.7.0) @@ -467,7 +467,7 @@ CHECKSUMS kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc - lexxy (0.8.5.beta) sha256=336b95507f80cb3ddad533c27edca8851c7773831a767495cdabfc984c4831ec + lexxy (0.9.0.beta) sha256=d64aa2beb09ff64400e45813a4893a7cbe875273198680d95d172fe11ebe829d lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04 diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f9ac840..84f8be7 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -200,3 +200,45 @@ html[lang="sv"] [data-locale='sv'], html[lang="uk"] [data-locale='uk'] { display: revert; } + + +main { + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 0.75rem; +} + +article { + background-color: var(--clr-white); + padding: 2rem 1.5rem; + max-width: 48ch; + + & > *:first-child { + margin-block-start: 0; + } +} + +h2 { + line-height: 1.2; + margin-block-end: 1em; +} + +p { + margin-block-end: 1em; +} + +.action-container { + margin-block-start: 2em; +} + +button { + appearance: none; + border: none; + background-color: var(--clr-black); + color: var(--clr-white); + padding: 0.6em 1em; + cursor: pointer; + border-radius: 0.4em; +} \ No newline at end of file diff --git a/app/assets/stylesheets/forms.css b/app/assets/stylesheets/forms.css index 0dbd150..054f254 100644 --- a/app/assets/stylesheets/forms.css +++ b/app/assets/stylesheets/forms.css @@ -1096,9 +1096,11 @@ ul { align-self: center; } +.focus .ts-control, .ts-control { border: none; padding: 8px 0; + box-shadow: none; } .ts-dropdown, .ts-control, .ts-control input { diff --git a/app/controllers/admin/nodes_controller.rb b/app/controllers/admin/nodes_controller.rb index 723b346..b3b6234 100644 --- a/app/controllers/admin/nodes_controller.rb +++ b/app/controllers/admin/nodes_controller.rb @@ -1,5 +1,4 @@ class Admin::NodesController < Admin::AdminController - before_action :set_node, only: %i[ edit update destroy children sort toggle ] helper_method :current_node_id, :open_node_ids @@ -33,15 +32,14 @@ class Admin::NodesController < Admin::AdminController # POST /nodes or /nodes.json def create - @node = Node.new(node_params) @node.status = :status_draft - base_title = t('ui.untitled') + base_title = t("ui.untitled") title = base_title count = 0 - while @node.siblings.exists?(title: {I18n.locale => title}) + while @node.siblings.exists?(title: { I18n.locale => title }) count += 1 title = "#{base_title} #{count}" end @@ -54,7 +52,7 @@ class Admin::NodesController < Admin::AdminController session[:open_node_ids] << @node.id unless session[:open_node_ids].include?(@node.id) format.turbo_stream - format.html { redirect_to edit_admin_node_url(@node), notice: t('ui.category_created', category: t(@node.template, scope: 'nodes.templates')) } + format.html { redirect_to edit_admin_node_url(@node), notice: t("ui.category_created", category: t(@node.template, scope: "nodes.templates")) } else format.html { render :new, status: :unprocessable_entity } end @@ -74,15 +72,14 @@ class Admin::NodesController < Admin::AdminController # PATCH/PUT /nodes/1 def update - params[:node][:expires_at] ||= nil respond_to do |format| if @node.update(node_params) format.turbo_stream { - flash.now[:notice] = t('ui.category_updated', category: t(@node.template, scope: 'nodes.templates')) + flash.now[:notice] = t("ui.category_updated", category: t(@node.template, scope: "nodes.templates")) } - format.html { redirect_to edit_node_url(@node), notice: t('ui.category_updated', category: t(@node.category, scope: 'nodes.categories')) } + format.html { redirect_to edit_node_url(@node), notice: t("ui.category_updated", category: t(@node.category, scope: "nodes.categories")) } else format.html { render :edit, status: :unprocessable_entity } end @@ -90,7 +87,7 @@ class Admin::NodesController < Admin::AdminController end - # DELETE /nodes/1 + # DELETE /nodes/1 def destroy if @node.destroy! @destroyed_node = @node @@ -98,9 +95,9 @@ class Admin::NodesController < Admin::AdminController end respond_to do |format| format.turbo_stream { - flash.now[:notice] = t('ui.category_destroyed', category: t(@destroyed_node.category, scope: 'nodes.categories')) + flash.now[:notice] = t("ui.category_destroyed", category: t(@destroyed_node.category, scope: "nodes.categories")) } - format.html { redirect_to nodes_url, notice: t(:'nodes.destroyed') } + format.html { redirect_to nodes_url, notice: t(:"nodes.destroyed") } end end @@ -124,8 +121,6 @@ class Admin::NodesController < Admin::AdminController end - - private @@ -188,7 +183,4 @@ private session[:open_node_ids] << @node.id unless session[:open_node_ids].include?(@node.id) end end - - - end diff --git a/app/controllers/concerns/quiz_helper_methods.rb b/app/controllers/concerns/quiz_helper_methods.rb index ba72499..dbf51ec 100644 --- a/app/controllers/concerns/quiz_helper_methods.rb +++ b/app/controllers/concerns/quiz_helper_methods.rb @@ -5,51 +5,16 @@ module QuizHelperMethods before_action :require_player! before_action :set_locale - helper_method :current_player, - :questions, - :question, - :questions_size, - :question_index, - :player_question_answer, - :result_node + helper_method :current_player end private - def questions - @questions ||= Node.at_depth(1).tmpl_article.viewable.ordered.to_a - end - - def result_node - @result_node ||= Node.at_depth(1).tmpl_list.viewable.first - end - - - def question - @question ||= questions[params[:id].to_i-1] - end - - - def player_question_answer - @player_question_answer ||= Answer.find_by(player_id: current_player.id, node_id: question.id)&.value - end - - - def questions_size - @questions_size ||= questions.size - end - - - def question_index - @question_index ||= (params[:id].to_i) - end - - def require_player! unless player_present? - redirect_to url_for(controller: "players", action: "new") + redirect_to url_for(controller: "languages", action: "index") end end diff --git a/app/controllers/direct_uploads_controller.rb b/app/controllers/direct_uploads_controller.rb index db6cae0..c8c5bdd 100644 --- a/app/controllers/direct_uploads_controller.rb +++ b/app/controllers/direct_uploads_controller.rb @@ -6,5 +6,4 @@ class DirectUploadsController < ActiveStorage::DirectUploadsController def authenticate! head :unauthorized unless User.enabled.admin_role.find_by(id: session[:user_id]).present? end - end diff --git a/app/controllers/players_controller.rb b/app/controllers/players_controller.rb new file mode 100644 index 0000000..ce9b0d3 --- /dev/null +++ b/app/controllers/players_controller.rb @@ -0,0 +1,14 @@ +class PlayersController < ApplicationController + include QuizHelperMethods + + skip_before_action :require_player! + + def create + reset_session + + @player = Player.create(locale: I18n.locale) + session[:player_id] = @player.id + + redirect_to stage_path(id: 1) + end +end diff --git a/app/controllers/stages_controller.rb b/app/controllers/stages_controller.rb new file mode 100644 index 0000000..cf18223 --- /dev/null +++ b/app/controllers/stages_controller.rb @@ -0,0 +1,36 @@ +class StagesController < ApplicationController + include QuizHelperMethods + + before_action :set_locale + before_action :require_player! + before_action :set_node, only: [ :show, :flip, :choose ] + + def show + @progress = current_player.progress[params[:id].to_s] || {} + end + + def flip + outcome = rand < 0.5 ? "chance" : "choice" + current_player.record_flip(params[:id], outcome) + redirect_to stage_path(id: params[:id]) + end + + def choose + option_index = params[:option].to_i + current_player.record_option(params[:id], option_index) + + # Advance to next stage + current_player.advance_to_next_stage! + redirect_to stage_path(id: current_player.current_stage) + end + +private + + def set_node + @node = Node.at_depth(1).viewable.ordered[params[:id].to_i - 1] + + if @node.nil? + redirect_to root_path + end + end +end diff --git a/app/helpers/admin/nodes_helper.rb b/app/helpers/admin/nodes_helper.rb index 731e9df..13eb912 100644 --- a/app/helpers/admin/nodes_helper.rb +++ b/app/helpers/admin/nodes_helper.rb @@ -1,13 +1,11 @@ module Admin::NodesHelper - - def node_structure_for_select(parent, node) result = [] - result << [parent.title, parent.id] if parent.root? + result << [ parent.title, parent.id ] if parent.root? parent.children.ordered.each do |child| - result << ["#{"-" * (child.depth)} #{child.title}".html_safe, child.id, { disabled: (child == node or child.ancestor_ids.include?(node.id)) }] + result << [ "#{"-" * (child.depth)} #{child.title}".html_safe, child.id, { disabled: (child == node or child.ancestor_ids.include?(node.id)) } ] result = result + node_structure_for_select(child, node) end @@ -17,7 +15,7 @@ module Admin::NodesHelper def spacer_node(node) return if node.root? - tag.div class: 'spacer' do + tag.div class: "spacer" do node.depth.times do concat tag.div nil end @@ -26,29 +24,27 @@ module Admin::NodesHelper def toggle_node(node) - return tag.div nil, class: 'child' if node.document? - link_to url_for(controller: 'nodes', action: 'children', id: node.id), - class: 'child parent', + return tag.div nil, class: "child" if node.document? + link_to url_for(controller: "nodes", action: "children", id: node.id), + class: "child parent", data: { turbo_stream: true, controller: "nodes", action: "click->nodes#toggle_children" } do - concat tag.span('expand_more') - concat tag.span('keyboard_arrow_right') + concat tag.span("expand_more") + concat tag.span("keyboard_arrow_right") end end def tree_title(node) - - - # result = [link_to(ENV['PROJECT_NAME'], + # result = [link_to(ENV["PROJECT_NAME"], # url_for(url_base_options.merge(id: nil)), - # class: "list-title-link#{' has-popup-menu' if node.blank?}", + # class: "list-title-link#{" has-popup-menu" if node.blank?}", # data: { - # icon: 'museum', - # action: node.blank? ? 'click->popup#toggle' : 'click->nodes#set_current', + # icon: "museum", + # action: node.blank? ? "click->popup#toggle" : "click->nodes#set_current", # turbo_stream: node.blank? ? nil : true, # })] result = [] @@ -57,31 +53,31 @@ module Admin::NodesHelper result << tree_node_title_link(n, node) end - result[-1] = "#{tag.div "#{result[-1]}#{node_popup_menu(node)}".html_safe, data: {controller: 'popup'} }" + result[-1] = "#{tag.div "#{result[-1]}#{node_popup_menu(node)}".html_safe, data: { controller: "popup" } }" - result.join(tag.span('>')).html_safe + result.join(tag.span(">")).html_safe end - def node_popup_menu(node=nil) - tag.div class: 'popup-menu' do + def node_popup_menu(node = nil) + tag.div class: "popup-menu" do tag.ul do - concat tag.li(link_to t('ui.edit'), edit_admin_node_path(node), data: {icon: 'edit', turbo_action: 'advance'}) if node + concat tag.li(link_to t("ui.edit"), edit_admin_node_path(node), data: {icon: "edit", turbo_action: "advance"}) if node Node.categories.each do |node_category| - concat tag.li button_to(t(node_category, scope: 'nodes.new_categories'), - url_for(controller: 'nodes', action: 'create'), - params: {node: {parent_id: node&.id}}, + concat tag.li button_to(t(node_category, scope: "nodes.new_categories"), + url_for(controller: "nodes", action: "create"), + params: { node: { parent_id: node&.id } }, data: { - action: 'click->popup#close_open', - icon: t(node_category, scope: 'nodes.icons') + action: "click->popup#close_open", + icon: t(node_category, scope: "nodes.icons") }) end - # concat tag.li button_to(t('ui.reindex'), - # url_for(controller: 'nodes', action: 'reindex', id: node.id), + # concat tag.li button_to(t("ui.reindex"), + # url_for(controller: "nodes", action: "reindex", id: node.id), # method: :patch, # data: { - # action: 'click->popup#close_open', - # icon: 'refresh' + # action: "click->popup#close_open", + # icon: "refresh" # }) if node end end @@ -89,7 +85,6 @@ module Admin::NodesHelper def node_link_title(node) - # return node.title if !node.document? or node.attachments.blank? or !node.attachments.first.asset.representable? # asset = node.attachments.first.asset @@ -97,49 +92,47 @@ module Admin::NodesHelper node_icon = nil node_title = tag.span(node.title) - (node_icon || '').concat(node_title).html_safe + (node_icon || "").concat(node_title).html_safe end def node_flags(node) return if node.copyright_with_inheritance.blank? and node.tags_with_inheritance.blank? - tag.div class: 'node__flags' do - concat tag.span('copyright') if node.copyright_with_inheritance.present? - concat tag.span('sell') if node.tags_with_inheritance.any? + tag.div class: "node__flags" do + concat tag.span("copyright") if node.copyright_with_inheritance.present? + concat tag.span("sell") if node.tags_with_inheritance.any? end end def drawer_node_link(node) link_to tag.span(node.title), - url_for(controller: 'nodes', action: node.document? ? 'edit' : 'tree', id: node.id), + url_for(controller: "nodes", action: node.document? ? "edit" : "tree", id: node.id), title: node.title, - class: 'node-title', - id: dom_id(node, 'drawer-link'), + class: "node-title", + id: dom_id(node, "drawer-link"), data: { nodes_id_param: node.id, turbo_stream: true, - action: 'click->nodes#set_current', + action: "click->nodes#set_current", icon: t("nodes.icons.#{node.category}") } end - def tree_node_title_link(node, current_node=nil) - + def tree_node_title_link(node, current_node = nil) current_node ||= node - url_base_options = {controller: 'nodes', action: 'tree'} + url_base_options = { controller: "nodes", action: "tree" } link_to(node.title, url_for(url_base_options.merge(id: node.id)), - class: current_node == node ? 'list-title-link has-popup-menu' : 'list-title-link', - id: dom_id(node, 'list-title-link'), + class: current_node == node ? "list-title-link has-popup-menu" : "list-title-link", + id: dom_id(node, "list-title-link"), data: { - action: current_node == node ? 'click->popup#toggle' : 'click->nodes#set_current', + action: current_node == node ? "click->popup#toggle" : "click->nodes#set_current", turbo_stream: current_node != node ? true : nil, nodes_id_param: node.id, icon: t("nodes.icons.#{node.category}") }) end - end diff --git a/app/helpers/stages_helper.rb b/app/helpers/stages_helper.rb new file mode 100644 index 0000000..93ca31e --- /dev/null +++ b/app/helpers/stages_helper.rb @@ -0,0 +1,2 @@ +module StagesHelper +end diff --git a/app/models/node.rb b/app/models/node.rb index fd52de1..abee89b 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -1,6 +1,5 @@ class Node < ApplicationRecord - - MENUS = %i"" + MENUS = %i[] COOKIE_POLICY = :cookie_policy SETTINGS = MENUS << COOKIE_POLICY @@ -14,7 +13,7 @@ class Node < ApplicationRecord translates :slug, :tags, locale_accessors: I18n.available_locales, - fallbacks: {zh: :en, hr: :en, cs: :en, da: :en, nl: :en, fi: :en, fr: :en, fr_ca: :en, de: :en, hu: :en, it: :en, ja: :en, ko: :en, nb: :en, pl: :en, pt: :en, ro: :en, sr: :en, sk: :en, sl: :en, es: :en, sv: :en, uk: :en} + fallbacks: { zh: :en, hr: :en, cs: :en, da: :en, nl: :en, fi: :en, fr: :en, fr_ca: :en, de: :en, hu: :en, it: :en, ja: :en, ko: :en, nb: :en, pl: :en, pt: :en, ro: :en, sr: :en, sk: :en, sl: :en, es: :en, sv: :en, uk: :en } translates :title, :url, @@ -22,14 +21,12 @@ class Node < ApplicationRecord :page_title, :page_description, locale_accessors: I18n.available_locales, - fallbacks: {zh: :en, hr: :en, cs: :en, da: :en, nl: :en, fi: :en, fr: :en, fr_ca: :en, de: :en, hu: :en, it: :en, ja: :en, ko: :en, nb: :en, pl: :en, pt: :en, ro: :en, sr: :en, sk: :en, sl: :en, es: :en, sv: :en, uk: :en} - - + fallbacks: { zh: :en, hr: :en, cs: :en, da: :en, nl: :en, fi: :en, fr: :en, fr_ca: :en, de: :en, hu: :en, it: :en, ja: :en, ko: :en, nb: :en, pl: :en, pt: :en, ro: :en, sr: :en, sk: :en, sl: :en, es: :en, sv: :en, uk: :en } enum :status, { status_published: 0, status_draft: 1, status_archived: 2 } enum :template, { - tmpl_article: 0, + tmpl_stage: 0, tmpl_chance: 1, tmpl_choice: 2, tmpl_bonus: 3 @@ -37,9 +34,9 @@ class Node < ApplicationRecord include PgSearch::Model pg_search_scope :pg_search, - against: {title: 'A', url: 'B', page_title: 'A', page_description: 'B', href: 'B', slug: 'B' }, + against: { title: "A", url: "B", page_title: "A", page_description: "B", href: "B", slug: "B" }, associated_against: { - attachments: [:body, :url] + attachments: [ :body, :url ] } before_validation :remove_empty_tags @@ -51,7 +48,7 @@ class Node < ApplicationRecord allow_nil: true scope :ordered, -> { order(position: :asc) } - scope :by_title, ->(rev) { order(Arel.sql(rev ? "title->>'#{I18n.locale.to_s}' DESC, id DESC": "title ->>'#{I18n.locale.to_s}' ASC, id ASC")) } + scope :by_title, ->(rev) { order(Arel.sql(rev ? "title->>'#{I18n.locale}' DESC, id DESC": "title ->>'#{I18n.locale}' ASC, id ASC")) } scope :by_status, ->(rev) { order(status: rev ? :desc : :asc, id: rev ? :desc : :asc) } scope :by_slug, ->(rev) { order(ancestry: rev ? :desc : :asc, position: rev ? :desc : :asc, id: rev ? :desc : :asc) } scope :by_last_modified, ->(rev) { order(updated_at: rev ? :asc : :desc, id: rev ? :desc : :asc) } @@ -62,10 +59,10 @@ class Node < ApplicationRecord where("(cardinality(excluded_locales) = 0 AND is_allowlist = false) OR (is_allowlist = true AND ? = ANY(excluded_locales)) OR (is_allowlist = false AND NOT(? = ANY(excluded_locales)))", - I18n.locale.to_s, I18n.locale.to_s ) + I18n.locale.to_s, I18n.locale.to_s) } - scope :viewable, -> { status_published.for_current_locale.where('published_at <= ? AND (expires_at IS NULL OR expires_at > ?)', Time.current, Time.current) } + scope :viewable, -> { status_published.for_current_locale.where("published_at <= ? AND (expires_at IS NULL OR expires_at > ?)", Time.current, Time.current) } scope :of_template, ->(tmpl) { where(template: Node.templates[tmpl.to_s]) } scope :with_setting, ->(setting) { setting.kind_of?(Array) ? where("settings && ?", "{#{setting.join(',')}}") : where("settings @> ?", "{#{setting}}") } @@ -77,25 +74,25 @@ class Node < ApplicationRecord def self.tags result = {} I18n.available_locales.each do |locale| - key = locale.to_s.downcase.sub('-','_') - result[key] = pluck(Arel.sql("tags->'#{locale}'")).flatten.compact.uniq.sort.reject{|v| v.blank?} + key = locale.to_s.downcase.sub("-", "_") + result[key] = pluck(Arel.sql("tags->'#{locale}'")).flatten.compact.uniq.sort.reject { |v| v.blank? } end result end def self.categories - ['document'] + [ "document" ] end def category - self.children.any? ? 'folder' : 'document' + self.children.any? ? "folder" : "document" end def document? - self.category == 'document' + self.category == "document" end @@ -126,66 +123,16 @@ class Node < ApplicationRecord def node_type return "site" if self.site? - 'tile' + "tile" end - def answer_percentages - # Get all valid answers (non-nil values) - valid_answers = answers.where.not(value: nil) - - # Count answers by value - value_counts = valid_answers.group(:value).count - - # Calculate total number of answers - total_answers = valid_answers.count - - # Initialize with 0 for both expected values - percentages = {0 => 0, 1 => 0} - - # Return initial percentages if no answers - return percentages if total_answers.zero? - - # Calculate exact percentages first (not rounded) - exact_percentages = {} - value_counts.each do |value, count| - # Only consider values 0 and 1 - next unless [0, 1].include?(value) - exact_percentages[value] = count.to_f / total_answers * 100 - end - - # First round down all percentages - [0, 1].each do |value| - percentages[value] = (exact_percentages[value] || 0).floor - end - - # Calculate how much we're off by due to rounding - remaining = 100 - percentages.values.sum - - # Distribute the remaining percentage points to values with the largest fractional parts - if remaining > 0 - # Filter exact percentages to only include 0 and 1 - sorted_by_fraction = exact_percentages - .select { |k, _| [0, 1].include?(k) } - .sort_by { |_, p| p - p.floor } - .reverse - - # Add 1 to the values with the largest fractional parts - sorted_by_fraction.take(remaining).each do |value, _| - percentages[value] += 1 - end - end - - # Return the percentages hash with keys 0 and 1 - percentages - end - private + def remove_empty_tags - %w"settings excluded_locales tags_en tags_zh tags_hr tags_cs tags_da tags_nl tags_fi tags_fr tags_fr_ca tags_de tags_hu tags_it tags_ja tags_ko tags_nb tags_pl tags_pt tags_ro tags_sr tags_sk tags_sl tags_es tags_sv tags_uk ".map do |k| + %w[settings excluded_locales tags_en tags_zh tags_hr tags_cs tags_da tags_nl tags_fi tags_fr tags_fr_ca tags_de tags_hu tags_it tags_ja tags_ko tags_nb tags_pl tags_pt tags_ro tags_sr tags_sk tags_sl tags_es tags_sv tags_uk].map do |k| self.send "#{k}=", self.send(k).reject { |v| v.blank? } unless self.send(k).blank? end end - end diff --git a/app/models/player.rb b/app/models/player.rb new file mode 100644 index 0000000..7e79444 --- /dev/null +++ b/app/models/player.rb @@ -0,0 +1,26 @@ +class Player < ApplicationRecord + attribute :progress, :json, default: {} + attribute :current_stage, :integer, default: 1 + attribute :score, :integer, default: 0 + + # stage_index: 1, 2, 3... + # outcome: 'chance' or 'choice' + def record_flip(stage_index, outcome) + self.progress[stage_index.to_s] ||= {} + self.progress[stage_index.to_s]["flip"] = outcome + save + end + + # stage_index: 1, 2, 3... + # option_index: 1, 2, or 3 + def record_option(stage_index, option_index) + self.progress[stage_index.to_s] ||= {} + self.progress[stage_index.to_s]["option"] = option_index + save + end + + def advance_to_next_stage! + self.current_stage += 1 + save + end +end diff --git a/app/views/languages/_intro.html.erb b/app/views/languages/_intro.html.erb index 675d80c..e69de29 100644 --- a/app/views/languages/_intro.html.erb +++ b/app/views/languages/_intro.html.erb @@ -1,20 +0,0 @@ - -<% @node.attachments.limit(2).each_with_index do |attachment, i| %> -
- <% if i == 0 %> -
- <%= attachment.body %> -
- <% else %> -
- <%= attachment.body.html_safe %> -
- <% end %> -
-<% end %> - -
-<%= link_to tag.span(t('get_started')), - url_for(controller: 'players', action: 'new', locale: I18n.locale), - class: 'button__base' %> -
\ No newline at end of file diff --git a/app/views/languages/index.html.erb b/app/views/languages/index.html.erb index b065a57..b4f513c 100644 --- a/app/views/languages/index.html.erb +++ b/app/views/languages/index.html.erb @@ -1,3 +1,14 @@ <%- content_for :title, t('project_name') %> -

Intro

\ No newline at end of file +
+ + <% @node.attachments.each do |attachment| %> + <%= attachment.body.html_safe %> + <% end %> + +
+ <%= button_to t("let_me_try"), + start_path(locale: I18n.locale), + method: :post %> +
+
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2550ef9..94e8307 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -16,7 +16,7 @@ - <%= stylesheet_link_tag "application" %> + <%= stylesheet_link_tag "reset", "application" %> diff --git a/app/views/site/tmpl_article.html.erb b/app/views/site/tmpl_article.html.erb deleted file mode 100644 index a23e2bb..0000000 --- a/app/views/site/tmpl_article.html.erb +++ /dev/null @@ -1,11 +0,0 @@ -<%- - - attachment = @node.attachments.limit(1)[0] - - article_parts = parse_article_html(attachment&.body) - - content_for :title, @node.page_title.blank? ? article_parts[:title] : @node.page_title - content_for :meta_description, @node.page_description.blank? ? article_parts[:description] : @node.page_description -%> - -<%= session[:player_id] %> \ No newline at end of file diff --git a/app/views/site/tmpl_list.html.erb b/app/views/site/tmpl_chance.html.erb similarity index 100% rename from app/views/site/tmpl_list.html.erb rename to app/views/site/tmpl_chance.html.erb diff --git a/app/views/site/tmpl_index.html.erb b/app/views/site/tmpl_choise.html.erb similarity index 100% rename from app/views/site/tmpl_index.html.erb rename to app/views/site/tmpl_choise.html.erb diff --git a/app/views/stages/show.html.erb b/app/views/stages/show.html.erb new file mode 100644 index 0000000..133694e --- /dev/null +++ b/app/views/stages/show.html.erb @@ -0,0 +1,19 @@ +<%- + content_for :title, @node.page_title.blank? ? @node.title : @node.page_title + content_for :meta_description, @node.page_description +%> + + +
+ + <% @node.attachments.each do |attachment| %> + <%= attachment.body.html_safe %> + <% end %> + +
+ <%= button_to t("flip"), url_for(action: 'flip'), method: :post %> +
+ + <%= current_player.inspect %> + +
\ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 854f8c5..a682e55 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,23 +1,10 @@ en: domain: completesentences.ikeafoundation.org - project_name: IKEA Foundation Week 2025 + project_name: IKEA Foundation Week 2026 client_name: IKEA Foundation - get_started: Let’s go! - what_is_your_name: What’s your name? - write_your_name: Write your name. - submit: Submit - next_question: Next question - of_people_worldwide_think_just_lik_you: of people worldwide think like you. - see_results: See results - results: Results - read_more: Read more - share_on_story: Share - read_more_link: https://ikeafoundation.org/25 - please_type_your_name_to_continue: Please type your name to continue - share_title: IKEA Foundation Week quiz result - share_text: Quiz result description - + let_me_try: Let me try! + languages: en: English @@ -210,8 +197,8 @@ en: en: English de: German templates: - tmpl_article: Side - tmpl_chance: Chance + tmpl_stage: Stage + tmpl_chance: Chance tmpl_choice: Choice tmpl_bonus: Bonus chance categories: diff --git a/config/routes.rb b/config/routes.rb index dece0b4..b84faf1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,11 +5,10 @@ Rails.application.routes.draw do # Can be used by load balancers and uptime monitors to verify that the app is live. # get "up" => "rails/health#show", as: :rails_health_check - get 'admin', to: redirect('/admin/en') + get "admin", to: redirect("/admin/en") namespace :admin do - scope ':locale', constraints: { locale: /en|zh|hr|cs|da|nl|fi|fr|fr-CA|de|hu|it|ja|ko|nb|pl|pt|ro|sr|sk|sl|es|sv|uk/ } do - + scope ":locale", constraints: { locale: /en|zh|hr|cs|da|nl|fi|fr|fr-CA|de|hu|it|ja|ko|nb|pl|pt|ro|sr|sk|sl|es|sv|uk/ } do # Assets resources :assets do collection do @@ -36,40 +35,32 @@ Rails.application.routes.draw do resources :users # Root - root to: 'nodes#index' + root to: "nodes#index" end # Cache - delete 'cache/clear', to: "admin#clear_cache" + delete "cache/clear", to: "admin#clear_cache" - resources :sessions, path: 'login', only: [:index, :create, :destroy] - get "logout", to: 'sessions#destroy', as: "logout" + resources :sessions, path: "login", only: [ :index, :create, :destroy ] + get "logout", to: "sessions#destroy", as: "logout" # 2 step auth - post 'login/verify', to: 'sessions#verify' - get 'login/verification', to: 'sessions#verification' + post "login/verify", to: "sessions#verify" + get "login/verification", to: "sessions#verification" end - scope ':locale', constraints: { locale: /en|zh|hr|cs|da|nl|fi|fr|fr-CA|de|hu|it|ja|ko|nb|pl|pt|ro|sr|sk|sl|es|sv|uk/ } do - - get 'player', to: 'players#new' - post 'player', to: 'players#create' + scope ":locale", constraints: { locale: /en|zh|hr|cs|da|nl|fi|fr|fr-CA|de|hu|it|ja|ko|nb|pl|pt|ro|sr|sk|sl|es|sv|uk/ } do + post "start", to: "players#create", as: "start" - post 'q/:id/answer', to: 'answers#create' - patch 'q/:id/answer', to: 'answers#update' - get 'q/:id/answer', to: 'questions#answer' - get 'q/:id', to: 'questions#show' + get "stages/:id", to: "stages#show", constraints: { id: /\d+/ }, as: :stage + post "stages/:id/flip", to: "stages#flip", constraints: { id: /\d+/ }, as: :flip_stage + post "stages/:id/choose", to: "stages#choose", constraints: { id: /\d+/ }, as: :choose_stage - post 'score', to: 'questions#score' - get 'result/:id', to: 'questions#shared_result' - get 'result', to: 'questions#result' - - get '', to: 'languages#show' - # get '*url', to: 'site#page', constraints: lambda { |req| req.path.exclude?('storage') } + get "", to: "languages#show" + # get "*url", to: "site#page", constraints: lambda { |req| req.path.exclude?("storage") } end - put 'update_locale', to: 'languages#update' - + put "update_locale", to: "languages#update" # Defines the root path route ("/") root "languages#index" diff --git a/db/migrate/20240424093536_create_attachments.rb b/db/migrate/20240424093536_create_attachments.rb index 0ee63ca..e3f24ab 100644 --- a/db/migrate/20240424093536_create_attachments.rb +++ b/db/migrate/20240424093536_create_attachments.rb @@ -1,7 +1,6 @@ class CreateAttachments < ActiveRecord::Migration[7.1] def change create_table :attachments do |t| - t.references 'asset' t.integer "attachable_for_id" @@ -14,9 +13,8 @@ class CreateAttachments < ActiveRecord::Migration[7.1] t.integer "position" t.timestamps - t.index ["attachable_for_id", "attachable_for_type"], name: "attachable_for" - t.index ["is_favorite"], name: "index_attachments_on_is_favorite" - + t.index [ "attachable_for_id", "attachable_for_type" ], name: "attachable_for" + t.index [ "is_favorite" ], name: "index_attachments_on_is_favorite" end end end diff --git a/db/migrate/20250527114853_create_players.rb b/db/migrate/20250527114853_create_players.rb new file mode 100644 index 0000000..5d8da4a --- /dev/null +++ b/db/migrate/20250527114853_create_players.rb @@ -0,0 +1,10 @@ +class CreatePlayers < ActiveRecord::Migration[8.1] + def change + create_table :players do |t| + t.text :locale + t.integer :current_stage, index: true + t.jsonb :progress, default: {} + t.timestamps + end + end +end diff --git a/db/migrate/20260323110134_add_missing_columns_to_players.rb b/db/migrate/20260323110134_add_missing_columns_to_players.rb new file mode 100644 index 0000000..fe9759b --- /dev/null +++ b/db/migrate/20260323110134_add_missing_columns_to_players.rb @@ -0,0 +1,9 @@ +class AddMissingColumnsToPlayers < ActiveRecord::Migration[8.1] + def change + add_column :players, :name, :text + add_column :players, :score, :integer + add_column :players, :answer_cache, :integer, array: true, default: [] + add_index :players, :answer_cache, using: :gin + add_index :players, :score + end +end diff --git a/db/schema.rb b/db/schema.rb index 7790712..3e4b07a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do +ActiveRecord::Schema[8.1].define(version: 2026_03_23_110134) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -117,11 +117,14 @@ ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do create_table "players", force: :cascade do |t| t.integer "answer_cache", default: [], array: true t.datetime "created_at", null: false + t.integer "current_stage" t.text "locale" t.text "name" + t.jsonb "progress", default: {} t.integer "score" t.datetime "updated_at", null: false t.index ["answer_cache"], name: "index_players_on_answer_cache", using: :gin + t.index ["current_stage"], name: "index_players_on_current_stage" t.index ["score"], name: "index_players_on_score" end diff --git a/test/controllers/stages_controller_test.rb b/test/controllers/stages_controller_test.rb new file mode 100644 index 0000000..5d7504f --- /dev/null +++ b/test/controllers/stages_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class StagesControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end