diff --git a/Gemfile.lock b/Gemfile.lock index db50a3b..e0e4daf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,29 +3,29 @@ GEM specs: action_text-trix (2.1.17) railties - actioncable (8.1.2) - actionpack (= 8.1.2) - activesupport (= 8.1.2) + actioncable (8.1.2.1) + actionpack (= 8.1.2.1) + activesupport (= 8.1.2.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.2) - actionpack (= 8.1.2) - activejob (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + actionmailbox (8.1.2.1) + actionpack (= 8.1.2.1) + activejob (= 8.1.2.1) + activerecord (= 8.1.2.1) + activestorage (= 8.1.2.1) + activesupport (= 8.1.2.1) mail (>= 2.8.0) - actionmailer (8.1.2) - actionpack (= 8.1.2) - actionview (= 8.1.2) - activejob (= 8.1.2) - activesupport (= 8.1.2) + actionmailer (8.1.2.1) + actionpack (= 8.1.2.1) + actionview (= 8.1.2.1) + activejob (= 8.1.2.1) + activesupport (= 8.1.2.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.2) - actionview (= 8.1.2) - activesupport (= 8.1.2) + actionpack (8.1.2.1) + actionview (= 8.1.2.1) + activesupport (= 8.1.2.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -33,36 +33,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.2) + actiontext (8.1.2.1) action_text-trix (~> 2.1.15) - actionpack (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + actionpack (= 8.1.2.1) + activerecord (= 8.1.2.1) + activestorage (= 8.1.2.1) + activesupport (= 8.1.2.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.2) - activesupport (= 8.1.2) + actionview (8.1.2.1) + activesupport (= 8.1.2.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.1.2) - activesupport (= 8.1.2) + activejob (8.1.2.1) + activesupport (= 8.1.2.1) globalid (>= 0.3.6) - activemodel (8.1.2) - activesupport (= 8.1.2) - activerecord (8.1.2) - activemodel (= 8.1.2) - activesupport (= 8.1.2) + activemodel (8.1.2.1) + activesupport (= 8.1.2.1) + activerecord (8.1.2.1) + activemodel (= 8.1.2.1) + activesupport (= 8.1.2.1) timeout (>= 0.4.0) - activestorage (8.1.2) - actionpack (= 8.1.2) - activejob (= 8.1.2) - activerecord (= 8.1.2) - activesupport (= 8.1.2) + activestorage (8.1.2.1) + actionpack (= 8.1.2.1) + activejob (= 8.1.2.1) + activerecord (= 8.1.2.1) + activesupport (= 8.1.2.1) marcel (~> 1.0) - activesupport (8.1.2) + activesupport (8.1.2.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -138,9 +138,6 @@ GEM actionview (>= 7.0.0) activesupport (>= 7.0.0) json (2.19.2) - json-schema (6.2.0) - addressable (~> 2.8) - bigdecimal (>= 3.1, < 5) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -168,8 +165,6 @@ GEM net-pop net-smtp marcel (1.1.0) - mcp (0.9.0) - json-schema (>= 4.1) mini_magick (5.3.1) logger mini_mime (1.1.5) @@ -252,20 +247,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.2) - actioncable (= 8.1.2) - actionmailbox (= 8.1.2) - actionmailer (= 8.1.2) - actionpack (= 8.1.2) - actiontext (= 8.1.2) - actionview (= 8.1.2) - activejob (= 8.1.2) - activemodel (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + rails (8.1.2.1) + actioncable (= 8.1.2.1) + actionmailbox (= 8.1.2.1) + actionmailer (= 8.1.2.1) + actionpack (= 8.1.2.1) + actiontext (= 8.1.2.1) + actionview (= 8.1.2.1) + activejob (= 8.1.2.1) + activemodel (= 8.1.2.1) + activerecord (= 8.1.2.1) + activestorage (= 8.1.2.1) + activesupport (= 8.1.2.1) bundler (>= 1.15.0) - railties (= 8.1.2) + railties (= 8.1.2.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -273,9 +268,9 @@ GEM rails-html-sanitizer (1.7.0) loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.1.2) - actionpack (= 8.1.2) - activesupport (= 8.1.2) + railties (8.1.2.1) + actionpack (= 8.1.2.1) + activesupport (= 8.1.2.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -297,11 +292,10 @@ GEM io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) - rubocop (1.85.1) + rubocop (1.86.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - mcp (~> 0.6) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) @@ -411,17 +405,17 @@ DEPENDENCIES CHECKSUMS action_text-trix (2.1.17) sha256=b44691639d77e67169dc054ceacd1edc04d44dc3e4c6a427aa155a2beb4cc951 - actioncable (8.1.2) sha256=dc31efc34cca9cdefc5c691ddb8b4b214c0ea5cd1372108cbc1377767fb91969 - actionmailbox (8.1.2) sha256=058b2fb1980e5d5a894f675475fcfa45c62631103d5a2596d9610ec81581889b - actionmailer (8.1.2) sha256=f4c1d2060f653bfe908aa7fdc5a61c0e5279670de992146582f2e36f8b9175e9 - actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423 - actiontext (8.1.2) sha256=0bf57da22a9c19d970779c3ce24a56be31b51c7640f2763ec64aa72e358d2d2d - actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b - activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825 - activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e - activerecord (8.1.2) sha256=acfbe0cadfcc50fa208011fe6f4eb01cae682ebae0ef57145ba45380c74bcc44 - activestorage (8.1.2) sha256=8a63a48c3999caeee26a59441f813f94681fc35cc41aba7ce1f836add04fba76 - activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae + actioncable (8.1.2.1) sha256=a2f88cecce148b3fcb63d2e517d7694e119830a85baa7d6cf59e5453dcf32e8d + actionmailbox (8.1.2.1) sha256=c2e45c0c1e5687e35e050838c94a8aed0d954c56a32ea411d54cd848c338c54e + actionmailer (8.1.2.1) sha256=d7d62fbc2197f1a7006bb5af4c665edf999adf79ab6c10337c088d27e6622071 + actionpack (8.1.2.1) sha256=a6b69cd10ec4c8d978c8eee51206e34152b1c1be017e534236dbc89a3d00ffb8 + actiontext (8.1.2.1) sha256=1e503ce600a6ab2e12a46f999959a7d8e2fdaff910ca01dcf3b968934b55d957 + actionview (8.1.2.1) sha256=38daa7b87bca427e2967f139e5b7f0d1081271bdafd0e015d8ef97a006f570a6 + activejob (8.1.2.1) sha256=c89c311d07fd358b76c581ed8fee87c5b4351fb44994f3389385c014d22182fe + activemodel (8.1.2.1) sha256=8f31a6f9c12fecb8e5a0fce8a8950cfd94f0d75829322935f99e8217a3e5f3c6 + activerecord (8.1.2.1) sha256=3f79140318ff6d23376f5d9b1b5b5e2c7d3cc8979dd71367e9a8394378ca630a + activestorage (8.1.2.1) sha256=36794c9b8853ac9276b0386cb1f8973374d8e71e8a9666bb02e70f5b7c9c5391 + activesupport (8.1.2.1) sha256=beec20ced12ad569194554399449a6372fdab03061b8f48a9ed6ef9b7dc251b2 acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8 addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485 ancestry (5.1.0) sha256=8a073cf6f7e306eeed36af72595abd19602ef4a197bf4beda2f31cf8f55de27b @@ -461,7 +455,6 @@ CHECKSUMS irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae jbuilder (2.14.1) sha256=4eb26376ff60ef100cb4fd6fd7533cd271f9998327e86adf20fd8c0e69fabb42 json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf - json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666 kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 @@ -473,7 +466,6 @@ CHECKSUMS loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04 mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941 marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee - mcp (0.9.0) sha256=a0a3737b0ac9df0772f4ef7e2b013c260ddbcf217a5d50a66bff0baeddf03e47 mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d @@ -517,10 +509,10 @@ CHECKSUMS rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9 rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868 - rails (8.1.2) sha256=5069061b23dfa8706b9f0159ae8b9d35727359103178a26962b868a680ba7d95 + rails (8.1.2.1) sha256=93ebf1efc792c9bc47e9795259c920312d3920008dad3ae634b7a0457ffe0af8 rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89 - railties (8.1.2) sha256=1289ece76b4f7668fc46d07e55cc992b5b8751f2ad85548b7da351b8c59f8055 + railties (8.1.2.1) sha256=f4d902869541af4e5b5552d726062fa59ec0fd9078f7ab87720dbd93f22c43ee rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 @@ -529,7 +521,7 @@ CHECKSUMS regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb - rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77 + rubocop (1.86.0) sha256=4ff1186fe16ebe9baff5e7aad66bb0ad4cabf5cdcd419f773146dbba2565d186 rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834 rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2 diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 0049354..452c93c 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -100,8 +100,7 @@ bottom: 0; left: 0; font: 0.75rem ui-monospace; - padding: 1em; - max-width: 40ch; + padding: 1em; } .turbo-progress-bar { diff --git a/app/controllers/admin/nodes_controller.rb b/app/controllers/admin/nodes_controller.rb index b3b6234..bac66fa 100644 --- a/app/controllers/admin/nodes_controller.rb +++ b/app/controllers/admin/nodes_controller.rb @@ -33,7 +33,7 @@ class Admin::NodesController < Admin::AdminController # POST /nodes or /nodes.json def create @node = Node.new(node_params) - @node.status = :status_draft + @node.status = :status_published base_title = t("ui.untitled") title = base_title diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 983eb41..aff3ca0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,9 +1,6 @@ class ApplicationController < ActionController::Base - - http_basic_authenticate_with name: "stupid", password: "studio" if Rails.env.production? - private @@ -13,8 +10,6 @@ private def not_found - raise ActionController::RoutingError.new('Not Found') + raise ActionController::RoutingError.new("Not Found") end - - end diff --git a/app/controllers/concerns/quiz_helper_methods.rb b/app/controllers/concerns/quiz_helper_methods.rb index dbf51ec..adf70aa 100644 --- a/app/controllers/concerns/quiz_helper_methods.rb +++ b/app/controllers/concerns/quiz_helper_methods.rb @@ -14,7 +14,7 @@ private def require_player! unless player_present? - redirect_to url_for(controller: "languages", action: "index") + redirect_to url_for(controller: "languages", action: "index") and return end end diff --git a/app/controllers/players_controller.rb b/app/controllers/players_controller.rb index 2a8526c..3f6cc6f 100644 --- a/app/controllers/players_controller.rb +++ b/app/controllers/players_controller.rb @@ -1,8 +1,9 @@ class PlayersController < ApplicationController include QuizHelperMethods - skip_before_action :require_player! + skip_before_action :require_player!, only: [ :create ] + # POST def create # reset_session @@ -11,4 +12,15 @@ class PlayersController < ApplicationController redirect_to stage_path(id: 1) end + + # POST + def go_again + @player = Player.create( + locale: current_player.locale, + extra_lives_stage: current_player.extra_lives_stage + ) + 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 index db01513..dd46b61 100644 --- a/app/controllers/stages_controller.rb +++ b/app/controllers/stages_controller.rb @@ -4,19 +4,29 @@ class StagesController < ApplicationController before_action :set_locale before_action :require_player! - before_action :set_stage + before_action :check_stage_access, expect: [ :game_over ] + before_action :set_stage, expect: [ :game_over ] + before_action :set_outcome, only: [ :reveal, :pick, :result ] + helper_method :stage_index + # GET def show end + # GET + def game_over + @node = Node.at_depth(1).game_over.viewable&.first + end + + # POST def flip outcome = rand < 0.5 ? "chance" : "choice" - current_player.record_flip(params[:id], outcome) + current_player.record_flip(stage_index, outcome) redirect_to url_for(action: "reveal") end @@ -38,13 +48,13 @@ class StagesController < ApplicationController @node = @stage.children.viewable.find_by(template: @outcome) possible_answers = @node.children.viewable - if @outcome == "chance" result = possible_answers.pluck(:template).sample elsif @outcome == "choice" result = possible_answers.find_by(id: params[:option])&.template end - current_player.record_pick_result(params[:id], result) unless result.blank? + current_player.record_pick_result(stage_index, result) unless result.blank? + redirect_to action: "result" end @@ -54,30 +64,90 @@ class StagesController < ApplicationController def result redirect_to root_path and return if @outcome.blank? - @result = current_player.progress[params[:id].to_s]["result"] + @result = current_player.progress[stage_index.to_s]["result"] + @node = @stage.children.viewable.find_by(template: @outcome)&.children&.viewable&.find_by(template: @result) - question_node = @stage.children.viewable.find_by(template: @outcome) - @node = question_node.children.viewable.find_by(template: @result) + current_player.update(is_done: true) if @node.game_over? + redirect_to root_path and return if @node.blank? + end - logger.info(current_player.inspect) + + # POST + def next + if player_can_proceed? + current_player.update(current_stage: stage_index + 1) + redirect_to stage_path(id: current_player.current_stage) + else + redirect_to root_path + end end -private + # POST + def bonus + @result = current_player.progress[stage_index.to_s]["result"] + redirect_to root_path unless @result == "bonus" + # Set extra lives stage + current_player.update( + extra_lives_stage: ((rand < 0.5) ? [ stage_index - 1, 0 ].max : stage_index), + is_done: true + ) - def set_stage - @stage = Node.at_depth(1).viewable.ordered[params[:id].to_i - 1] + # Reset progress + # current_player.reset_when_game_over - if @stage.nil? - redirect_to root_path + redirect_to action: "game_over" + end + + + # POST + def get_extra_life + current_player.update( + extra_lives_stage: [ stage_index - 1, 0 ].max, + is_done: true + ) + + redirect_to action: "game_over" + end + + + +private + + + def check_stage_access + if stage_index > current_player.current_stage + redirect_to root_path and return end end + + def player_can_proceed? + result = current_player.progress[stage_index.to_s]&.dig("result") + + return false if stage_index != current_player.current_stage + + return true if %w[good best].include?(result) + return true if result == "bad" and current_player.extra_lives_stage >= stage_index + + false + end + + + def set_stage + @stage = Node.at_depth(1).stage.viewable.ordered[stage_index - 1] + redirect_to root_path and return if @stage.nil? + end + + def set_outcome - @outcome ||= current_player.progress[params[:id].to_s]["flip"] + @outcome ||= current_player.progress[stage_index.to_s]&.dig("flip") end + def stage_index + @stage_index ||= params[:id].to_i + end end diff --git a/app/javascript/image_controller.js b/app/javascript/image_controller.js deleted file mode 100644 index ec9d568..0000000 --- a/app/javascript/image_controller.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - //static targets = ["img"] - - - connect() { - - if (this.element.complete && this.element.naturalHeight !== 0) { - this.element.classList.add('loaded') - } else { - this.element.addEventListener('load', function() { - this.classList.add('loaded') - }) - } - - } - - disconnect() { - } - - -} \ No newline at end of file diff --git a/app/javascript/quiz_preloader.js b/app/javascript/quiz_preloader.js deleted file mode 100644 index e7c267c..0000000 --- a/app/javascript/quiz_preloader.js +++ /dev/null @@ -1,117 +0,0 @@ -export default class QuizImagePreloader { - constructor() { - this.isPreloading = false - this.preloadedImages = new Map() - } - - init() { - // Start preloading on initial page load - document.addEventListener('DOMContentLoaded', () => { - sessionStorage.removeItem('quiz_images_preloaded') - // this.startPreloadingFromHeader() - // console.info('startPreloadingFromHeader'); - }) - - // Continue preloading on Turbo navigation - document.addEventListener('turbo:load', () => { - this.startPreloadingFromHeader() - console.info('startPreloadingFromHeader'); - }) - - - } - - startPreloadingFromHeader() { - - if (sessionStorage.getItem('quiz_images_preloaded') === 'true') { - return - } - - // Skip if already preloading - if (this.isPreloading) { - return - } - - const imageUrls = this.getImageUrlsFromHeader() - - if (imageUrls.length > 0) { - this.preloadImages(imageUrls) - } - } - - getImageUrlsFromHeader() { - const headerElement = document.querySelector('header[data-quiz-images]') || - document.querySelector('[data-quiz-images]') - - if (!headerElement) return [] - - - try { - const urls = JSON.parse(headerElement.dataset.quizImages || '[]') - return urls.filter(url => url && url.trim()) - } catch (e) { - console.warn('Failed to parse quiz image URLs from header:', e) - return [] - } - } - - preloadImages(imageUrls) { - this.isPreloading = true - let loadedCount = 0 - const totalImages = imageUrls.length - - imageUrls.forEach((url, index) => { - // Skip if already preloaded - if (this.preloadedImages.has(url)) { - loadedCount++ - if (loadedCount === totalImages) { - this.onPreloadComplete() - } - return - } - - const img = new Image() - - img.onload = () => { - this.preloadedImages.set(url, img) - loadedCount++ - - if (loadedCount === totalImages) { - this.onPreloadComplete() - } - } - - img.onerror = () => { - console.warn(`Failed to preload image: ${url}`) - loadedCount++ - - if (loadedCount === totalImages) { - this.onPreloadComplete() - } - } - - // Stagger requests to avoid overwhelming server - setTimeout(() => { - img.src = url - }, index * 30) - }) - } - - onPreloadComplete() { - this.isPreloading = false - sessionStorage.setItem('quiz_images_preloaded', 'true') - - console.info('PreloadComplete'); - // Dispatch completion event for components that need to know - document.dispatchEvent(new CustomEvent('quiz:preload:complete', { - detail: { totalImages: this.preloadedImages.size } - })) - } - - // Check if preloading is complete - isComplete() { - return sessionStorage.getItem('quiz_images_preloaded') === 'true' - } - - -} \ No newline at end of file diff --git a/app/models/node.rb b/app/models/node.rb index e4cee29..e6a8d3a 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -42,12 +42,12 @@ class Node < ApplicationRecord case depth when 1 - [ :stage ] + [ :stage, :game_over ] when 2 - [ :choice, :chance, :bonus ] + [ :choice, :chance ] when 3 if parent&.chance? - [ :good, :bad, :game_over ] + [ :good, :bad, :game_over, :bonus ] elsif parent&.choice? [ :best, :good, :bad ] else @@ -66,6 +66,7 @@ class Node < ApplicationRecord } before_validation :remove_empty_tags + before_validation :normalize_translations validates_presence_of :title @@ -161,4 +162,14 @@ private self.send "#{k}=", self.send(k).reject { |v| v.blank? } unless self.send(k).blank? end end + + + def normalize_translations + %w[title slug].each do |attr| + value = read_attribute(attr) + if value.is_a?(Hash) + write_attribute(attr, value.transform_values { |v| v.is_a?(String) ? v.strip : v }) + end + end + end end diff --git a/app/models/player.rb b/app/models/player.rb index ebeaefb..f126d23 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -1,7 +1,23 @@ class Player < ApplicationRecord - attribute :progress, :json, default: {} attribute :current_stage, :integer, default: 1 - attribute :score, :integer, default: 0 + attribute :extra_lives_stage, :integer, default: 0 + attribute :progress, :json, default: {} + attribute :is_done, :boolean, default: false + + scope :playing, -> { where(is_done: false) } + scope :done, -> { where(is_done: true) } + + + def is_playing? + !self.is_done? + end + + + def reset_when_game_over + self.progress = {} + self.current_stage = 1 + self.save + end # stage_index: 1, 2, 3... # outcome: 'chance' or 'choice' @@ -11,13 +27,7 @@ class Player < ApplicationRecord 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 + # stage_index: 1, 2, 3... # result: 'good', 'bad', 'game_over' @@ -27,8 +37,5 @@ class Player < ApplicationRecord save end - def advance_to_next_stage! - self.current_stage += 1 - save - end + end diff --git a/app/models/user.rb b/app/models/user.rb index d244176..10542bb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,10 +1,9 @@ class User < ApplicationRecord - - enum :role, { user: 'user', admin: 'admin' }, suffix: true + enum :role, { user: "user", admin: "admin" }, suffix: true include PgSearch::Model - pg_search_scope :pg_search, against: [:lastname, :firstname, :email, :phone, :title], - using: {tsearch: {dictionary: "danish"}} + pg_search_scope :pg_search, against: [ :lastname, :firstname, :email, :phone, :title ], + using: { tsearch: { dictionary: "danish" } } has_secure_password has_many :verification_codes, dependent: :destroy @@ -16,7 +15,7 @@ class User < ApplicationRecord validates_uniqueness_of :email validates_format_of :email, with: URI::MailTo::EMAIL_REGEXP - normalizes :email, with: -> email { email.strip.downcase } + normalizes :email, with: ->(email) { email.strip.downcase } validate :cant_change_admin, on: :update @@ -31,18 +30,18 @@ class User < ApplicationRecord def su? - email == 'mattias@oncotype.dk' + email == "mattias@oncotype.dk" end def name return email if lastname.blank? and firstname.blank? - [firstname, lastname].select{ |v| !v.blank? }.join(' ') + [ firstname, lastname ].select { |v| !v.blank? }.join(" ") end def initials - name.split(' ').map{ |s| s[0] }.join('').mb_chars.upcase + name.split(" ").map { |s| s[0] }.join("").mb_chars.upcase end @@ -50,21 +49,21 @@ class User < ApplicationRecord !self.enabled_at.nil? end + protected - #Prevent the user admins from beeing changed + # Prevent the user admins from beeing changed def cant_change_admin user = self.class.find(self.id) - errors.add(:email, I18n.t(:you_cant_change_the_email_on_this_user, scope: 'users')) if user.su? and self.email != user.email - errors.add(:email, I18n.t(:you_cant_change_this_on_this_user, scope: 'users')) if user.su? and !self.admin_role? and self.role_changed? - errors.add(:email, I18n.t(:you_cant_disable_this_user, scope: 'users')) if user.su? and self.enabled_at.nil? + errors.add(:email, I18n.t(:you_cant_change_the_email_on_this_user, scope: "users")) if user.su? and self.email != user.email + errors.add(:email, I18n.t(:you_cant_change_this_on_this_user, scope: "users")) if user.su? and !self.admin_role? and self.role_changed? + errors.add(:email, I18n.t(:you_cant_disable_this_user, scope: "users")) if user.su? and self.enabled_at.nil? end # Prevents the super user admin to be removed" def dont_destroy_admin - raise I18n.t(:cant_destroy_this_user, scope: 'users') if self.su? + raise I18n.t(:cant_destroy_this_user, scope: "users") if self.su? end - end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index ba1216c..fe911e6 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -8,7 +8,6 @@ <%= tag.meta name: 'description', content: content_for?(:meta_description) ? yield(:meta_description) : (@node.present?? @node.page_description : '') %> - <%= csrf_meta_tags %> <%= csp_meta_tag %> @@ -16,11 +15,8 @@ - <%= stylesheet_link_tag "reset", "application" %> - - + <%= stylesheet_link_tag "reset", "application" %> -
diff --git a/app/views/stages/choice.html.erb b/app/views/stages/choice.html.erb index 667fe89..bad492c 100644 --- a/app/views/stages/choice.html.erb +++ b/app/views/stages/choice.html.erb @@ -12,7 +12,7 @@ <%= attachment.body.html_safe %> <% end %> -
+
<% @node.children.viewable.ordered.each do |answer_option| %> <%= button_to answer_option.title, url_for(action: 'pick', option: answer_option.id), method: :post %> <% end %> diff --git a/app/views/stages/game_over.html.erb b/app/views/stages/game_over.html.erb new file mode 100644 index 0000000..27e5bae --- /dev/null +++ b/app/views/stages/game_over.html.erb @@ -0,0 +1,18 @@ +<%- + content_for :title, @node.page_title.blank? ? @node.title : @node.page_title + content_for :meta_description, @node.page_description + + content_for :debug, current_player.inspect +%> + + +
+ + <% @node.attachments.each do |attachment| %> + <%= attachment.body.html_safe %> + <% end %> + +
+ <%= button_to t("go_again"), go_again_path(), method: :post %> +
+
\ No newline at end of file diff --git a/app/views/stages/result.html.erb b/app/views/stages/result.html.erb index 8efef99..38f76f0 100644 --- a/app/views/stages/result.html.erb +++ b/app/views/stages/result.html.erb @@ -12,4 +12,29 @@ <%= attachment.body.html_safe %> <% end %> +
+ <% case @result %> + <% when "best", "good" %> + <%= button_to t("next_stage"), url_for(action: 'next'), method: :post %> + + <% when "bad" %> + <% if current_player.extra_lives_stage >= stage_index %> + <%= button_to t("use_extra_life"), url_for(action: 'next'), method: :post %> + <% else %> + <% if stage_index > 1 %> + <%= button_to t("get_extra_life"), url_for(action: 'get_extra_life'), method: :post %> + <% else %> + <%= button_to t("go_again"), go_again_path(), method: :post %> + <% end %> + <% end %> + + <% when "bonus" %> + <%= button_to t("bonus"), url_for(action: 'bonus'), method: :post %> + + <% when "game_over" %> + <%= button_to t("go_again"), go_again_path(), method: :post %> + + <% end %> +
+ \ No newline at end of file diff --git a/config/importmap.rb b/config/importmap.rb index 3c499f3..ad7e597 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -15,6 +15,4 @@ pin "tom-select/dist/js/tom-select.base.min.js", to: "tom-select--dist--js--tom- # site_helper pin "application", preload: false pin "locale_controller", preload: false -pin "image_controller", preload: false -pin "quiz_preloader", preload: false pin "plausible_controller", preload: false diff --git a/config/locales/en.yml b/config/locales/en.yml index 35c13df..ccd1a88 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4,6 +4,11 @@ en: client_name: IKEA Foundation let_me_try: Let me try! + next_stage: Proceed to the next stage + bonus: Bonus + go_again: Go again + use_extra_life: Use extra life + get_extra_life: Get extra life languages: @@ -205,6 +210,7 @@ en: bad: Bad game_over: Game Over good: Good + bonus: Bonus categories: box: Box diff --git a/config/routes.rb b/config/routes.rb index 5f271f5..85d1870 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -50,18 +50,26 @@ Rails.application.routes.draw do 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 - post "start", to: "players#create", as: "start" + post "start", to: "players#create", as: :start + post "go_again", to: "players#go_again", as: :go_again scope "stages/:id", constraints: { id: /\d+/ } do - get "", to: "stages#show", as: "stage" + get "", to: "stages#show", as: :stage post "flip", to: "stages#flip" get "reveal", to: "stages#reveal" post "pick(/:option)", to: "stages#pick" get "result", to: "stages#result" + + post "next", to: "stages#next" + post "bonus", to: "stages#bonus" + + post "get_extra_life", to: "stages#get_extra_life" end + get "game_over", to: "stages#game_over" + get "", to: "languages#show" # get "*url", to: "site#page", constraints: lambda { |req| req.path.exclude?("storage") } end diff --git a/db/migrate/20250527114853_create_players.rb b/db/migrate/20250527114853_create_players.rb index 5d8da4a..12792f4 100644 --- a/db/migrate/20250527114853_create_players.rb +++ b/db/migrate/20250527114853_create_players.rb @@ -3,7 +3,9 @@ class CreatePlayers < ActiveRecord::Migration[8.1] create_table :players do |t| t.text :locale t.integer :current_stage, index: true + t.integer :extra_lives_stage, index: true t.jsonb :progress, default: {} + t.boolean :is_done, default: false, index: true t.timestamps end end diff --git a/db/migrate/20260323110134_add_missing_columns_to_players.rb b/db/migrate/20260323110134_add_missing_columns_to_players.rb deleted file mode 100644 index fe9759b..0000000 --- a/db/migrate/20260323110134_add_missing_columns_to_players.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 3e4b07a..533ab8a 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: 2026_03_23_110134) do +ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -115,17 +115,16 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_23_110134) do end 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.integer "extra_lives_stage" + t.boolean "is_done", default: false 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" + t.index ["extra_lives_stage"], name: "index_players_on_extra_lives_stage" + t.index ["is_done"], name: "index_players_on_is_done" end create_table "quiz_results", force: :cascade do |t| diff --git a/test/models/node_whitespace_test.rb b/test/models/node_whitespace_test.rb new file mode 100644 index 0000000..98e9483 --- /dev/null +++ b/test/models/node_whitespace_test.rb @@ -0,0 +1,25 @@ +require "test_helper" + +class NodeWhitespaceTest < ActiveSupport::TestCase + test "title and slug should strip whitespace" do + node = Node.new( + title_en: " English Title ", + slug_en: " english-slug ", + title_fr: " Titre Français ", + slug_fr: " titre-francais " + ) + node.valid? + + # These should be stripped after implemention + # assert_equal "English Title", node.title_en + # assert_equal "english-slug", node.slug_en + # assert_equal "Titre Français", node.title_fr + # assert_equal "titre-francais", node.slug_fr + + # Current behavior (before implementation) + puts "Current title_en: '#{node.title_en}'" + puts "Current slug_en: '#{node.slug_en}'" + puts "Current title_fr: '#{node.title_fr}'" + puts "Current slug_fr: '#{node.slug_fr}'" + end +end diff --git a/vendor/javascript/@orchidjs--sifter.js b/vendor/javascript/@orchidjs--sifter.js new file mode 100644 index 0000000..0fb4475 --- /dev/null +++ b/vendor/javascript/@orchidjs--sifter.js @@ -0,0 +1,17 @@ +// @orchidjs/sifter@1.1.0 downloaded from https://ga.jspm.io/npm:@orchidjs/sifter@1.1.0/dist/esm/sifter.js + +import{scoreValue as t,iterate as e,cmp as r,propToArray as i,getAttrNesting as n,getAttr as s}from"./utils.js";import{escape_regex as o,getPattern as c}from"@orchidjs/unicode-variants";export{getPattern}from"@orchidjs/unicode-variants";import"./types.js";class Sifter{items;settings;constructor(t,e){this.items=t;this.settings=e||{diacritics:true}}tokenize(t,e,r){if(!t||!t.length)return[];const i=[];const n=t.split(/\s+/);var s;r&&(s=new RegExp("^("+Object.keys(r).map(o).join("|")+"):(.*)$"));n.forEach((t=>{let r;let n=null;let u=null;if(s&&(r=t.match(s))){n=r[1];t=r[2]}if(t.length>0){u=this.settings.diacritics?c(t)||null:o(t);u&&e&&(u="\\b"+u)}i.push({string:t,regex:u?new RegExp(u,"iu"):null,field:n})}));return i} +/** + * Returns a function to be used to score individual results. + * + * Good matches will have a higher score than poor matches. + * If an item is not a match, 0 will be returned by the function. + * + * @returns {T.ScoreFn} + */ +getScoreFunction(t,e){var r=this.prepareSearch(t,e);return this._getScoreFunction(r)} +/** + * @returns {T.ScoreFn} + * + */_getScoreFunction(r){const i=r.tokens,n=i.length;if(!n)return function(){return 0};const s=r.options.fields,o=r.weights,c=s.length,u=r.getAttrFn;if(!c)return function(){return 1};const f=function(){return c===1?function(e,r){const i=s[0].field;return t(u(r,i),e,o[i]||1)}:function(r,i){var n=0;if(r.field){const e=u(i,r.field);!r.regex&&e?n+=1/c:n+=t(e,r,1)}else e(o,((e,s)=>{n+=t(u(i,s),r,e)}));return n/c}}();return n===1?function(t){return f(i[0],t)}:r.options.conjunction==="and"?function(t){var e,r=0;for(let n of i){e=f(n,t);if(e<=0)return 0;r+=e}return r/n}:function(t){var r=0;e(i,(e=>{r+=f(e,t)}));return r/n}}getSortFunction(t,e){var r=this.prepareSearch(t,e);return this._getSortFunction(r)}_getSortFunction(t){var e,i=[];const n=this,s=t.options,o=!t.query&&s.sort_empty?s.sort_empty:s.sort;if(typeof o=="function")return o.bind(this);const get_field=function(e,r){return e==="$score"?r.score:t.getAttrFn(n.items[r.id],e)};if(o)for(let e of o)(t.query||e.field!=="$score")&&i.push(e);if(t.query){e=true;for(let t of i)if(t.field==="$score"){e=false;break}e&&i.unshift({field:"$score",direction:"desc"})}else i=i.filter((t=>t.field!=="$score"));const c=i.length;return c?function(t,e){var n,s;for(let o of i){s=o.field;let i=o.direction==="desc"?-1:1;n=i*r(get_field(s,t),get_field(s,e));if(n)return n}return 0}:null}prepareSearch(t,e){const r={};var o=Object.assign({},e);i(o,"sort");i(o,"sort_empty");if(o.fields){i(o,"fields");const t=[];o.fields.forEach((e=>{typeof e=="string"&&(e={field:e,weight:1});t.push(e);r[e.field]="weight"in e?e.weight:1}));o.fields=t}return{options:o,query:t.toLowerCase().trim(),tokens:this.tokenize(t,o.respect_word_boundaries,r),total:0,items:[],weights:r,getAttrFn:o.nesting?n:s}}search(t,r){var i,n,s=this;n=this.prepareSearch(t,r);r=n.options;t=n.query;const o=r.score||s._getScoreFunction(n);t.length?e(s.items,((t,e)=>{i=o(t);(r.filter===false||i>0)&&n.items.push({score:i,id:e})})):e(s.items,((t,e)=>{n.items.push({score:1,id:e})}));const c=s._getSortFunction(n);c&&n.items.sort(c);n.total=n.items.length;typeof r.limit==="number"&&(n.items=n.items.slice(0,r.limit));return n}}export{Sifter,r as cmp,s as getAttr,n as getAttrNesting,e as iterate,i as propToArray,t as scoreValue}; + diff --git a/vendor/javascript/@orchidjs--unicode-variants.js b/vendor/javascript/@orchidjs--unicode-variants.js new file mode 100644 index 0000000..9b14c64 --- /dev/null +++ b/vendor/javascript/@orchidjs--unicode-variants.js @@ -0,0 +1,11 @@ +// @orchidjs/unicode-variants@1.1.2 downloaded from https://ga.jspm.io/npm:@orchidjs/unicode-variants@1.1.2/dist/esm/index.js + +import{setToPattern as t,escape_regex as e,arrayToPattern as s,sequencePattern as n}from"./regex.js";import{allSubstrings as r}from"./strings.js";const o=[[0,65535]];const l="[̀-ͯ·ʾʼ]";let a;let u;const h=3;const c={};const i={"/":"⁄∕",0:"߀",a:"ⱥɐɑ",aa:"ꜳ",ae:"æǽǣ",ao:"ꜵ",au:"ꜷ",av:"ꜹꜻ",ay:"ꜽ",b:"ƀɓƃ",c:"ꜿƈȼↄ",d:"đɗɖᴅƌꮷԁɦ",e:"ɛǝᴇɇ",f:"ꝼƒ",g:"ǥɠꞡᵹꝿɢ",h:"ħⱨⱶɥ",i:"ɨı",j:"ɉȷ",k:"ƙⱪꝁꝃꝅꞣ",l:"łƚɫⱡꝉꝇꞁɭ",m:"ɱɯϻ",n:"ꞥƞɲꞑᴎлԉ",o:"øǿɔɵꝋꝍᴑ",oe:"œ",oi:"ƣ",oo:"ꝏ",ou:"ȣ",p:"ƥᵽꝑꝓꝕρ",q:"ꝗꝙɋ",r:"ɍɽꝛꞧꞃ",s:"ßȿꞩꞅʂ",t:"ŧƭʈⱦꞇ",th:"þ",tz:"ꜩ",u:"ʉ",v:"ʋꝟʌ",vy:"ꝡ",w:"ⱳ",y:"ƴɏỿ",z:"ƶȥɀⱬꝣ",hv:"ƕ"};for(let t in i){let e=i[t]||"";for(let s=0;s{a===void 0&&(a=generateMap(t||o))};const normalize=(t,e="NFKD")=>t.normalize(e);const asciifold=t=>Array.from(t).reduce(( +/** + * @param {string} result + * @param {string} char + */ +(t,e)=>t+_asciifold(e)),"");const _asciifold=t=>{t=normalize(t).toLowerCase().replace(d,(/** @type {string} */t=>c[t]||""));return normalize(t,"NFC")};function*generator(t){for(const[e,s]of t)for(let t=e;t<=s;t++){let e=String.fromCharCode(t);let s=asciifold(e);s!=e.toLowerCase()&&(s.length>h||s.length!=0&&(yield{folded:s,composed:e,code_point:t}))}}const generateSets=s=>{const n={};const addMatching=(s,r)=>{ +/** @type {Set} */ +const o=n[s]||new Set;const l=new RegExp("^"+t(o)+"$","iu");if(!r.match(l)){o.add(e(r));n[s]=o}};for(let t of generator(s)){addMatching(t.folded,t.folded);addMatching(t.folded,t.composed)}return n};const generateMap=n=>{const r=generateSets(n);const o={};let l=[];for(let s in r){let n=r[s];n&&(o[s]=t(n));s.length>1&&l.push(e(s))}l.sort(((t,e)=>e.length-t.length));const a=s(l);u=new RegExp("^"+a,"u");return o};const mapSequence=(t,e=1)=>{let s=0;t=t.map((t=>{a[t]&&(s+=t.length);return a[t]||t}));return s>=e?n(t):""};const substringsToPattern=(t,e=1)=>{e=Math.max(e,t.length-1);return s(r(t).map((t=>mapSequence(t,e))))};const sequencesToPattern=(t,e=true)=>{let r=t.length>1?1:0;return s(t.map((t=>{let s=[];const o=e?t.length():t.length()-1;for(let e=0;e{for(const s of e){if(s.start!=t.start||s.end!=t.end)continue;if(s.substrs.join("")!==t.substrs.join(""))continue;let e=t.parts;const filter=t=>{for(const s of e){if(s.start===t.start&&s.substr===t.substr)return false;if(t.length!=1&&s.length!=1){if(t.starts.start)return true;if(s.startt.start)return true}}return false};let n=s.parts.filter(filter);if(!(n.length>0))return true}return false};class Sequence{parts;substrs;start;end;constructor(){this.parts=[];this.substrs=[];this.start=0;this.end=0}add(t){if(t){this.parts.push(t);this.substrs.push(t.substr);this.start=Math.min(t.start,this.start);this.end=Math.max(t.end,this.end)}}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(t,e){let s=new Sequence;let n=JSON.parse(JSON.stringify(this.parts));let r=n.pop();for(const t of n)s.add(t);let o=e.substr.substring(0,t-r.start);let l=o.length;s.add({start:r.start,end:r.start+l,length:l,substr:o});return s}}const getPattern=t=>{initialize();t=asciifold(t);let e="";let s=[new Sequence];for(let n=0;n0){h=h.sort(((t,e)=>t.length()-e.length()));for(let t of h)inSequences(t,s)||s.push(t)}else if(n>0&&c.size==1&&!c.has("3")){e+=sequencesToPattern(s,false);let t=new Sequence;const n=s[0];n&&t.add(n.last());s=[t]}}e+=sequencesToPattern(s,true);return e};export{_asciifold,asciifold,o as code_points,e as escape_regex,generateMap,generateSets,generator,getPattern,initialize,mapSequence,normalize,substringsToPattern,a as unicode_map}; + diff --git a/vendor/javascript/tom-select.js b/vendor/javascript/tom-select.js new file mode 100644 index 0000000..6414c54 --- /dev/null +++ b/vendor/javascript/tom-select.js @@ -0,0 +1,4 @@ +// tom-select@2.4.3 downloaded from https://ga.jspm.io/npm:tom-select@2.4.3/dist/esm/tom-select.complete.js + +import i from"./tom-select.js";import o from"./plugins/change_listener/plugin.js";import n from"./plugins/checkbox_options/plugin.js";import r from"./plugins/clear_button/plugin.js";import e from"./plugins/drag_drop/plugin.js";import p from"./plugins/dropdown_header/plugin.js";import t from"./plugins/caret_position/plugin.js";import s from"./plugins/dropdown_input/plugin.js";import l from"./plugins/input_autogrow/plugin.js";import m from"./plugins/no_backspace_delete/plugin.js";import u from"./plugins/no_active_items/plugin.js";import g from"./plugins/optgroup_columns/plugin.js";import d from"./plugins/remove_button/plugin.js";import c from"./plugins/restore_on_backspace/plugin.js";import _ from"./plugins/virtual_scroll/plugin.js";import"./contrib/microevent.js";import"./contrib/microplugin.js";import"@orchidjs/sifter";import"@orchidjs/unicode-variants";import"./contrib/highlight.js";import"./vanilla.js";import"./utils.js";import"./constants.js";import"./getSettings.js";import"./defaults.js";i.define("change_listener",o);i.define("checkbox_options",n);i.define("clear_button",r);i.define("drag_drop",e);i.define("dropdown_header",p);i.define("caret_position",t);i.define("dropdown_input",s);i.define("input_autogrow",l);i.define("no_backspace_delete",m);i.define("no_active_items",u);i.define("optgroup_columns",g);i.define("remove_button",d);i.define("restore_on_backspace",c);i.define("virtual_scroll",_);export{i as default}; +