From 944b4551940fddd90556badc59d6fd3f7e296979 Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Fri, 2 Jun 2023 09:29:43 +0200 Subject: [PATCH] Introduce Dark Mode This commit mainly changes the color definitions. Mostly, those changes are semantically equally, but there are a few changes that occurred to align the color scheme within the app. --- app/assets/javascripts/application.js | 1 + .../javascripts/channels/la_exercises.js | 2 +- app/assets/javascripts/community_solution.js | 1 + app/assets/javascripts/editor.js | 9 ++ app/assets/javascripts/editor/editor.js.erb | 38 +++-- app/assets/javascripts/exercise_graphs.js | 30 ++-- app/assets/javascripts/exercises.js.erb | 19 ++- .../javascripts/markdown_ace_editor.js.erb | 7 +- .../javascripts/request_for_comments.js | 9 ++ app/assets/javascripts/shell.js | 10 +- .../javascripts/submission_statistics.js.erb | 27 ++-- app/assets/javascripts/working_time_graphs.js | 6 +- app/assets/stylesheets/base.css.scss | 25 ++-- .../bootstrap-dropdown-submenu.css.scss | 4 +- app/assets/stylesheets/editor.css.scss | 13 +- .../stylesheets/exercise_collections.scss | 14 +- app/assets/stylesheets/exercises.css.scss | 36 ++--- app/assets/stylesheets/forms.css.scss | 4 + .../stylesheets/request-for-comments.css.scss | 57 +++---- app/assets/stylesheets/statistics.css.scss | 57 +++---- app/javascript/application.js | 2 + app/javascript/chosen-dark.scss | 140 ++++++++++++++++++ app/javascript/highlight.js | 3 - app/javascript/highlight.scss | 9 ++ app/javascript/stylesheets.scss | 23 +++ app/javascript/vis.js | 3 - app/javascript/vis.scss | 19 +++ .../_breadcrumbs_and_title.html.slim | 2 +- .../_color_mode_selector.html.slim | 17 +++ .../application/_locale_selector.html.slim | 2 +- app/views/community_solutions/_form.html.slim | 2 +- app/views/consumers/show.html.slim | 2 +- .../execution_environments/show.html.slim | 8 +- app/views/exercises/_editor.html.slim | 6 +- .../exercises/_editor_file_tree.html.slim | 4 +- app/views/exercises/_editor_output.html.slim | 14 +- app/views/exercises/_tips_content.html.slim | 2 +- .../external_users/statistics.html.slim | 6 +- app/views/exercises/show.html.slim | 16 +- app/views/exercises/statistics.html.slim | 6 +- app/views/layouts/application.html.slim | 1 + app/views/proxy_exercises/show.html.slim | 2 +- .../_list_entry.html.slim | 2 +- .../request_for_comments/index.html.slim | 2 +- app/views/request_for_comments/show.html.slim | 2 +- app/views/shared/_form_filters.html.slim | 2 +- config/locales/de.yml | 9 +- config/locales/en.yml | 9 +- lib/assets/javascripts/color_mode_picker.js | 95 ++++++++++++ 49 files changed, 582 insertions(+), 197 deletions(-) create mode 100644 app/javascript/chosen-dark.scss create mode 100644 app/javascript/highlight.scss create mode 100644 app/javascript/vis.scss create mode 100644 app/views/application/_color_mode_selector.html.slim create mode 100644 lib/assets/javascripts/color_mode_picker.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 2a13291d..73e7c498 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -17,6 +17,7 @@ // // lib/assets //= require flash +//= require color_mode_picker // // vendor/assets //= require ace/ace diff --git a/app/assets/javascripts/channels/la_exercises.js b/app/assets/javascripts/channels/la_exercises.js index 1f47c552..bfb7e0e2 100644 --- a/app/assets/javascripts/channels/la_exercises.js +++ b/app/assets/javascripts/channels/la_exercises.js @@ -208,7 +208,7 @@ $(document).on('turbolinks:load', function() { .html(function(_event, _d) { const e = rect.nodes(); const i = e.indexOf(this) % learners.length; - return "Student: " + learners_name(i) + "
" + + return "Student: " + learners_name(i) + "
" + "0: " + learners_time(0, i) + "
" + "1: " + learners_time(1, i) + "
" + "2: " + learners_time(2, i) + "
" + diff --git a/app/assets/javascripts/community_solution.js b/app/assets/javascripts/community_solution.js index 68c62d17..6566d45a 100644 --- a/app/assets/javascripts/community_solution.js +++ b/app/assets/javascripts/community_solution.js @@ -20,6 +20,7 @@ $(document).on('turbolinks:load', function() { CodeOceanEditorSubmissions ) + $(document).on('theme:change:ace', CodeOceanEditor.handleAceThemeChangeEvent.bind(CodeOceanEditor)); $('#submit').one('click', CodeOceanEditorSubmissions.submitCode.bind(CodeOceanEditor)); $('#accept').one('click', CodeOceanEditorSubmissions.submitCode.bind(CodeOceanEditor)); } diff --git a/app/assets/javascripts/editor.js b/app/assets/javascripts/editor.js index 028ac69f..4f31c3ab 100644 --- a/app/assets/javascripts/editor.js +++ b/app/assets/javascripts/editor.js @@ -19,4 +19,13 @@ $(document).on('turbolinks:load', function(event) { // Search for insertLines and Turbolinks reload / cache control CodeOceanEditor.initializeEverything(); } + + function handleThemeChangeEvent(event) { + if (CodeOceanEditor) { + CodeOceanEditor.THEME = event.detail.currentTheme === 'dark' ? 'ace/theme/tomorrow_night' : 'ace/theme/tomorrow'; + document.dispatchEvent(new Event('theme:change:ace')); + } + } + + $(document).on('theme:change', handleThemeChangeEvent.bind(this)); }); diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index 401db40f..26f3eee2 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -2,7 +2,7 @@ var CodeOceanEditor = { //ACE-Editor-Path // ruby part adds the relative_url_root, if it is set. ACE_FILES_PATH: '<%= "#{Rails.application.config.relative_url_root.chomp('/')}/assets/ace/" %>', - THEME: 'ace/theme/textmate', + THEME: window.getCurrentTheme() === 'dark' ? 'ace/theme/tomorrow_night' : 'ace/theme/tomorrow', //Color-Encoding for Percentages in Progress Bars (For submissions) ADEQUATE_PERCENTAGE: 50, @@ -88,13 +88,13 @@ var CodeOceanEditor = { getCardClass: function (result) { if (result.file_role === 'teacher_defined_linter') { - return 'card bg-info text-white' + return 'bg-info text-white' } else if (result.stderr && !result.score) { - return 'card bg-danger text-white'; + return 'bg-danger text-white'; } else if (result.score < 1) { - return 'card bg-warning text-white'; + return 'bg-warning text-white'; } else { - return 'card bg-success text-white'; + return 'bg-success text-white'; } }, @@ -127,7 +127,7 @@ var CodeOceanEditor = { if (!filetree.hasClass('jstree-loading')) { filetree.jstree("deselect_all"); - filetree.jstree().select_node(file_id); + filetree.jstree(true).select_node(file_id); } else { setTimeout(CodeOceanEditor.selectFileInJsTree.bind(null, filetree, file_id), 250); } @@ -309,16 +309,12 @@ var CodeOceanEditor = { }); } - editor.commands.bindKey("ctrl+alt+0", null); this.editors.push(editor); this.editor_for_file.set($(element).parent().data('filename'), editor); var session = editor.getSession(); var mode = $(element).data('mode') session.setMode(mode); - if (mode === 'ace/mode/python') { - editor.setTheme('ace/theme/tomorrow') - } session.setTabSize($(element).data('indent-size')); session.setUseSoftTabs(true); session.setUseWrapMode(true); @@ -345,6 +341,12 @@ var CodeOceanEditor = { }.bind(this)); }, + handleAceThemeChangeEvent: function (event) { + this.editors.forEach(function (editor) { + editor.setTheme(this.THEME); + }.bind(this)); + }, + handleUTF16Surrogates: function (AceDeltaObject, AceSession) { if (AceDeltaObject.data === undefined || AceDeltaObject.data.action !== "removeText") { return; @@ -372,6 +374,7 @@ var CodeOceanEditor = { initializeEventHandlers: function () { $(document).on('click', '#results a', this.showOutput.bind(this)); $(document).on('keydown', this.handleKeyPress.bind(this)); + $(document).on('theme:change:ace', this.handleAceThemeChangeEvent.bind(this)); this.initializeFileTreeButtons(); this.initializeWorkspaceButtons(); this.initializeRequestForComments() @@ -398,9 +401,6 @@ var CodeOceanEditor = { }).fail(_.noop) .always(function () { ace.edit(editor).session.setMode(newMode); - if (newMode === 'ace/mode/python') { - ace.edit(editor).setTheme('ace/theme/tomorrow') - } }); }, @@ -411,7 +411,9 @@ var CodeOceanEditor = { } else { filesInstance = $('#files'); } - filesInstance.jstree(filesInstance.data('entries')); + const jsTreeConfig = filesInstance.data('entries') || {core: {}}; + jsTreeConfig.core.themes = {...jsTreeConfig.core.themes, name: window.getCurrentTheme() === "dark" ? "default-dark" : "default"} + filesInstance.jstree(jsTreeConfig); filesInstance.on('click', 'li.jstree-leaf > a', function (event) { const file_id = parseInt($(event.target).parent().attr('id')); const frame = $('[data-file-id="' + file_id + '"]').parent(); @@ -419,6 +421,11 @@ var CodeOceanEditor = { this.showFrame(frame); this.toggleButtonStates(); }.bind(this)); + $(document).on('theme:change', function(event) { + const newColorScheme = event.detail.currentTheme; + // Update the JStree theme + filesInstance.jstree(true).set_theme(newColorScheme === "dark" ? "default-dark" : "default"); + }); }, initializeFileTreeButtons: function () { @@ -539,7 +546,8 @@ var CodeOceanEditor = { }, populateCard: function (card, result, index) { - card.addClass(this.getCardClass(result)); + card.addClass('card'); + card.find('.card-header').addClass(this.getCardClass(result)); card.find('.card-title .filename').text(result.filename); card.find('.card-title .number').text(index + 1); card.find('.row .col-md-9').eq(0).find('.number').eq(0).text(result.passed); diff --git a/app/assets/javascripts/exercise_graphs.js b/app/assets/javascripts/exercise_graphs.js index 86014010..79421288 100644 --- a/app/assets/javascripts/exercise_graphs.js +++ b/app/assets/javascripts/exercise_graphs.js @@ -120,19 +120,19 @@ $(document).on('turbolinks:load', function() { var largestSubmittedTimeStamp = submissions[submissions_length-1]; var largestArrayForRange; - if(largestSubmittedTimeStamp.cause == "assess"){ + if(largestSubmittedTimeStamp.cause === "assess"){ largestArrayForRange = submissionsScoreAndTimeAssess; x.domain([0,largestArrayForRange[largestArrayForRange.length - 1][1]]).clamp(true); - } else if(largestSubmittedTimeStamp.cause == "submit"){ + } else if(largestSubmittedTimeStamp.cause === "submit"){ largestArrayForRange = submissionsScoreAndTimeSubmits; x.domain([0,largestArrayForRange[largestArrayForRange.length - 1][1]]).clamp(true); - } else if(largestSubmittedTimeStamp.cause == "run"){ + } else if(largestSubmittedTimeStamp.cause === "run"){ largestArrayForRange = submissionsScoreAndTimeRuns; x.domain([0,largestArrayForRange[largestArrayForRange.length - 1]]).clamp(true); - } else if(largestSubmittedTimeStamp.cause == "autosave"){ + } else if(largestSubmittedTimeStamp.cause === "autosave"){ largestArrayForRange = submissionsAutosaves; x.domain([0,largestArrayForRange[largestArrayForRange.length - 1]]).clamp(true); - } else if(largestSubmittedTimeStamp.cause == "save"){ + } else if(largestSubmittedTimeStamp.cause === "save"){ largestArrayForRange = submissionsSaves; x.domain([0,largestArrayForRange[largestArrayForRange.length - 1]]).clamp(true); } @@ -163,6 +163,7 @@ $(document).on('turbolinks:load', function() { .call(yAxis); svg.append("text") // y axis label + .attr("class", "y axis") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("dy", "-3em") @@ -180,12 +181,12 @@ $(document).on('turbolinks:load', function() { .style('font-size', 20) .style('text-decoration', 'underline'); - + svg.append("path") //.datum() .attr("class", "line") .attr('id', 'myPath')// new - .attr("stroke", "black") + .attr("stroke", "var(--bs-emphasis-color)") .attr("stroke-width", 5) .attr("fill", "none")// end new .attr("d", line(submissionsScoreAndTimeAssess));//--- @@ -194,7 +195,7 @@ $(document).on('turbolinks:load', function() { .datum(submissionsScoreAndTimeAssess) .attr("class", "line") .attr('id', 'myPath')// new - .attr("stroke", "orange") + .attr("stroke", "var(--bs-warning)") .attr("stroke-width", 5) .attr("fill", "none")// end new .attr("d", line);//--- @@ -203,6 +204,7 @@ $(document).on('turbolinks:load', function() { svg.selectAll("dot") // Add dots to assesses .data(submissionsScoreAndTimeAssess) .enter().append("circle") + .attr("fill", "var(--bs-secondary)") .attr("r", 3.5) .attr("cx", function(d) { return x(d[1]); }) .attr("cy", function(d) { return y(d[0]); }); @@ -216,14 +218,14 @@ $(document).on('turbolinks:load', function() { .data(submissionsScoreAndTimeSubmits) .enter().append("circle") .attr("r", 6) - .attr("stroke", "black") - .attr("fill", "blue") + .attr("stroke", "var(--bs-emphasis-color)") + .attr("fill", "var(--bs-blue)") .attr("cx", function(d) { return x(d[1]); }) .attr("cy", function(d) { return y(d[0]); }); for (var i = 0; i < submissionsScoreAndTimeRuns.length; i++) { svg.append("line") - .attr("stroke", "red") + .attr("stroke", "var(--bs-red)") .attr("stroke-width", 1) .attr("fill", "none")// end new .attr("y1", y(0)) @@ -232,9 +234,9 @@ $(document).on('turbolinks:load', function() { .attr("x2", x(submissionsScoreAndTimeRuns[i])); } - var color_hash = { 0 : ["Submissions", "blue"], - 1 : ["Assesses", "orange"], - 2 : ["Runs", "red"] + var color_hash = { 0 : ["Submissions", "var(--bs-blue)"], + 1 : ["Assesses", "var(--bs-orange)"], + 2 : ["Runs", "var(--bs-red)"] }; // add legend diff --git a/app/assets/javascripts/exercises.js.erb b/app/assets/javascripts/exercises.js.erb index 2709bdb0..57b64fbd 100644 --- a/app/assets/javascripts/exercises.js.erb +++ b/app/assets/javascripts/exercises.js.erb @@ -1,17 +1,13 @@ $(document).on('turbolinks:load', function () { - // ruby part adds the relative_url_root, if it is set. - var ACE_FILES_PATH = '<%= "#{Rails.application.config.relative_url_root.chomp('/')}/assets/ace/" %>'; - var THEME = 'ace/theme/textmate'; - var TAB_KEY_CODE = 9; var execution_environments; var file_types; - + const editors = []; var configureEditors = function () { _.each(['modePath', 'themePath', 'workerPath'], function (attribute) { - ace.config.set(attribute, ACE_FILES_PATH); + ace.config.set(attribute, CodeOceanEditor.ACE_FILES_PATH); }); }; @@ -28,7 +24,8 @@ $(document).on('turbolinks:load', function () { // document.removeLines(document.getLength() - 1, document.getLength() - 1); editor.setReadOnly($(element).data('read-only') !== undefined); editor.setShowPrintMargin(false); - editor.setTheme(THEME); + editor.setTheme(CodeOceanEditor.THEME); + editors.push(editor); // For creating / editing an exercise var textarea = $('textarea[id="exercise_files_attributes_' + index + '_content"]'); @@ -52,6 +49,14 @@ $(document).on('turbolinks:load', function () { session.setUseWrapMode(true); } + const handleAceThemeChangeEvent = function(event) { + editors.forEach(function (editor) { + editor.setTheme(CodeOceanEditor.THEME); + }.bind(this)); + }; + + $(document).on('theme:change:ace', handleAceThemeChangeEvent.bind(this)); + var initializeEditors = function () { // initialize ace editors for all code textareas in the dom except the last one. The last one is the dummy area for new files, which is cloned when needed. // this one must NOT! be initialized. diff --git a/app/assets/javascripts/markdown_ace_editor.js.erb b/app/assets/javascripts/markdown_ace_editor.js.erb index 9ac28df1..f5e0c80d 100644 --- a/app/assets/javascripts/markdown_ace_editor.js.erb +++ b/app/assets/javascripts/markdown_ace_editor.js.erb @@ -1,13 +1,14 @@ (function() { - var ACE_FILES_PATH = '<%= "#{Rails.application.config.relative_url_root.chomp('/')}/assets/ace/" %>'; - window.MarkdownEditor = function(selector) { - ace.config.set('modePath', ACE_FILES_PATH); + _.each(['modePath', 'themePath', 'workerPath'], function (attribute) { + ace.config.set(attribute, CodeOceanEditor.ACE_FILES_PATH); + }.bind(this)); var editor = ace.edit($(selector).next()[0]); editor.on('change', function() { $(selector).val(editor.getValue()); }); editor.setShowPrintMargin(false); + editor.setTheme(CodeOceanEditor.THEME); var session = editor.getSession(); session.setMode('ace/mode/markdown'); session.setUseWrapMode(true); diff --git a/app/assets/javascripts/request_for_comments.js b/app/assets/javascripts/request_for_comments.js index d999bcaa..54900fd5 100644 --- a/app/assets/javascripts/request_for_comments.js +++ b/app/assets/javascripts/request_for_comments.js @@ -70,6 +70,7 @@ $(document).on('turbolinks:load', function () { // set editor mode (used for syntax highlighting currentEditor.getSession().setMode($(editor).data('mode')); currentEditor.getSession().setOption("useWorker", false); + currentEditor.setTheme(CodeOceanEditor.THEME); currentEditor.commentVisualsByLine = {}; setAnnotations(currentEditor, $(editor).data('file-id')); @@ -77,6 +78,14 @@ $(document).on('turbolinks:load', function () { currentEditor.on("guttermousemove", showPopover); }); + const handleAceThemeChangeEvent = function() { + $('.editor').each(function (_, editor) { + ace.edit(editor).setTheme(CodeOceanEditor.THEME); + }.bind(this)); + }; + + $(document).on('theme:change:ace', handleAceThemeChangeEvent.bind(this)); + function preprocess(commentText) { // sanitize comments to deal with XSS attacks: commentText = $('div.sanitizer').text(commentText).html(); diff --git a/app/assets/javascripts/shell.js b/app/assets/javascripts/shell.js index 5e64c5d2..a1d1c822 100644 --- a/app/assets/javascripts/shell.js +++ b/app/assets/javascripts/shell.js @@ -99,13 +99,16 @@ $(document).on('turbolinks:load', function () { fileTree.removeClass('my-3 justify-content-center'); fileTree.jstree({ 'core': { + 'themes': { + 'name': window.getCurrentTheme() === "dark" ? "default-dark" : "default" + }, 'data': { 'url': function (node) { const params = {sudo: sudo.is(':checked')}; return Routes.list_files_in_execution_environment_path(id, params); }, 'data': function (node) { - return {'path': getPath(fileTree.jstree(), node)|| '/'}; + return {'path': getPath(fileTree.jstree(true), node)|| '/'}; } } } @@ -130,6 +133,11 @@ $(document).on('turbolinks:load', function () { window.location = downloadPath; } }.bind(this)); + $(document).on('theme:change', function(event) { + const newColorScheme = event.detail.currentTheme; + // Update the JStree theme + fileTree.jstree(true).set_theme(newColorScheme === "dark" ? "default-dark" : "default"); + }); } } diff --git a/app/assets/javascripts/submission_statistics.js.erb b/app/assets/javascripts/submission_statistics.js.erb index 1a57354b..7885654b 100644 --- a/app/assets/javascripts/submission_statistics.js.erb +++ b/app/assets/javascripts/submission_statistics.js.erb @@ -1,8 +1,4 @@ $(document).on('turbolinks:load', function(event) { - - var ACE_FILES_PATH = '<%= "#{Rails.application.config.relative_url_root.chomp('/')}/assets/ace/" %>'; - var THEME = 'ace/theme/textmate'; - var currentSubmission = 0; var active_file = undefined; var fileTrees = []; @@ -24,7 +20,7 @@ $(document).on('turbolinks:load', function(event) { var selectFileInJsTree = function() { if (!filetree.hasClass('jstree-loading')) { filetree.jstree("deselect_all"); - filetree.jstree().select_node(active_file.file_id); + filetree.jstree("select_node", active_file.file_id); } else { setTimeout(selectFileInJsTree, 250); } @@ -38,8 +34,10 @@ $(document).on('turbolinks:load', function(event) { var initializeFileTree = function() { $('.files').each(function(index, element) { - fileTree = $(element).jstree($(element).data('entries')); - fileTree.on('click', 'li.jstree-leaf', function() { + const jsTreeConfig = $(element).data('entries') + jsTreeConfig.core.themes = {...jsTreeConfig.core.themes, name: window.getCurrentTheme() === "dark" ? "default-dark" : "default"} + const fileTree = $(element).jstree(jsTreeConfig); + $(element).on('click', 'li.jstree-leaf', function() { var id = parseInt($(this).attr('id')); _.each(files[currentSubmission], function(file) { if (file.file_id === id) { @@ -48,6 +46,11 @@ $(document).on('turbolinks:load', function(event) { }); showActiveFile(); }); + $(document).on('theme:change', function(event) { + const newColorScheme = event.detail.currentTheme; + // Update the JStree theme + fileTree.jstree(true).set_theme(newColorScheme === "dark" ? "default-dark" : "default"); + }); fileTrees.push(fileTree); }); }; @@ -60,7 +63,7 @@ $(document).on('turbolinks:load', function(event) { if ($.isController('exercises') && $('#timeline').isPresent() && event.originalEvent.data.url.includes("/statistics")) { _.each(['modePath', 'themePath', 'workerPath'], function(attribute) { - ace.config.set(attribute, ACE_FILES_PATH); + ace.config.set(attribute, CodeOceanEditor.ACE_FILES_PATH); }); var slider = $('#submissions-slider>input'); @@ -72,7 +75,7 @@ $(document).on('turbolinks:load', function(event) { editor = ace.edit('current-file'); editor.setShowPrintMargin(false); - editor.setTheme(THEME); + editor.setTheme(CodeOceanEditor.THEME); editor.$blockScrolling = Infinity; editor.setReadOnly(true); @@ -90,6 +93,12 @@ $(document).on('turbolinks:load', function(event) { }); }); + const handleAceThemeChangeEvent = function() { + editor.setTheme(CodeOceanEditor.THEME); + }; + + $(document).on('theme:change:ace', handleAceThemeChangeEvent.bind(this)); + const onSliderChange = function(event) { currentSubmission = slider.val(); var currentFiles = files[currentSubmission]; diff --git a/app/assets/javascripts/working_time_graphs.js b/app/assets/javascripts/working_time_graphs.js index 7fa3c802..7d09169d 100644 --- a/app/assets/javascripts/working_time_graphs.js +++ b/app/assets/javascripts/working_time_graphs.js @@ -113,6 +113,7 @@ $(document).on('turbolinks:load', function() { .call(yAxis); svg.append("text") // y axis label + .attr("class", "y axis") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("dy", "-3em") @@ -134,7 +135,7 @@ $(document).on('turbolinks:load', function() { .datum(minutes_count) .attr("class", "line") .attr('id', 'myPath') - .attr("stroke", "orange") + .attr("stroke", "var(--bs-warning)") .attr("stroke-width", 5) .attr("fill", "none") .attr("d", line); @@ -216,7 +217,7 @@ $(document).on('turbolinks:load', function() { .attr('class', 'd3-tip') .offset([-10, 0]) .html(function(_event, d) { - return "Students: " + d + ""; + return "Students: " + d + ""; }); var svg = d3.select("#chart_2").append("svg") @@ -248,6 +249,7 @@ $(document).on('turbolinks:load', function() { .attr("dy", ".71em"); svg.append("text") + .attr("class", "y axis") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("dy", "-3em") diff --git a/app/assets/stylesheets/base.css.scss b/app/assets/stylesheets/base.css.scss index ff240cc2..c642c09c 100644 --- a/app/assets/stylesheets/base.css.scss +++ b/app/assets/stylesheets/base.css.scss @@ -9,7 +9,7 @@ h1, h2, h3, h4, h5, h6 { .lead { font-size: 16px; - color: rgba(70, 70, 70, 1); + color: var(--bs-dark-text-emphasis); } a:not(.dropdown-item, .dropdown-toggle, .dropdown-link, .btn, .page-link), .btn-link { @@ -26,10 +26,10 @@ i.fa-solid, i.fa-regular, i.fa-solid { } pre, .output-element { - background-color: #FAFAFA; + background-color: var(--bs-light-bg-subtle); margin: 0; padding: .25rem!important; - border: 1px solid #CCCCCC; + border: 1px solid var(--bs-border-color-translucent); } span.caret { @@ -50,14 +50,14 @@ span.caret { .progress { margin: 0; - border: 1px solid #CCCCCC; + border: 1px solid var(--bs-border-color-translucent); padding: 0.125rem !important; height: 1.25rem !important; .progress-bar { line-height: initial; min-width: 2em; - color: white; + color: var(--bs-white); } } @@ -96,10 +96,17 @@ span.caret { .flash { font-size: 100%; +} - a, a:hover { - color: white; - font-weight: bold; +html[data-bs-theme="dark"] { + .alert .alert-link:hover { + filter: brightness(135%); + } +} + +html[data-bs-theme="light"] { + .alert .alert-link:hover { + filter: brightness(175%); } } @@ -110,7 +117,7 @@ span.caret { .spinner { width: 40px; height: 40px; - background-color: #333; + background-color: var(--bs-body-color); margin: 100px auto; -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out; diff --git a/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss b/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss index ce930c28..562f77ce 100644 --- a/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss +++ b/app/assets/stylesheets/bootstrap-dropdown-submenu.css.scss @@ -20,13 +20,13 @@ border-color: transparent; border-style: solid; border-width: 5px 0 5px 5px; - border-left-color: #cccccc; + border-left-color: var(--bs-border-color-translucent); margin-top: 5px; margin-right: -10px; } .dropdown-submenu:hover > a:after { - border-left-color: #ffffff; + border-left-color: var(--bs-dropdown-link-active-color); } .dropdown-submenu.float-start { diff --git a/app/assets/stylesheets/editor.css.scss b/app/assets/stylesheets/editor.css.scss index 38ada147..224eba2c 100644 --- a/app/assets/stylesheets/editor.css.scss +++ b/app/assets/stylesheets/editor.css.scss @@ -6,8 +6,11 @@ .own-editor { height: 100%; width: 100%; - .ace_scroller .ace_content { - background: #FAFAFA; +} + +html[data-bs-theme="light"] { + .own-editor .ace_scroller .ace_content { + background-color: var(--bs-secondary-border-subtle); } } @@ -51,7 +54,7 @@ } #editor-buttons { - background-color: #008CBA; + background-color: var(--bs-primary); margin-top: 0; width: 100%; display: flex; @@ -97,7 +100,7 @@ visibility: hidden; margin-top: .2em; height: 1.6em; - color: #777; + color: var(--bs-tertiary-color); font-size: 0.8em; } @@ -188,7 +191,7 @@ #error-hints { display: none; - background-color: #FAFAFA; + background-color: var(--bs-light-bg-subtle); .heading { font-weight: bold; diff --git a/app/assets/stylesheets/exercise_collections.scss b/app/assets/stylesheets/exercise_collections.scss index 1b3c6c76..681812f2 100644 --- a/app/assets/stylesheets/exercise_collections.scss +++ b/app/assets/stylesheets/exercise_collections.scss @@ -1,7 +1,7 @@ -$time-color: #008cba; -$min-color: #8efa00; -$avg-color: #ffca00; -$max-color: #ff2600; +$time-color: var(--bs-blue); +$min-color: var(--bs-yellow); +$avg-color: var(--bs-teal); +$max-color: var(--bs-red); path.line.minimum-working-time { stroke: $min-color; @@ -31,7 +31,7 @@ rect.value-bar { .box { width: 20px; height: 20px; - border: solid 1px #000; + border: solid 1px var(--bs-emphasis-color); } .box.time { @@ -62,8 +62,8 @@ rect.value-bar { display: none; min-width: 80px; height: auto; - background: none repeat scroll 0 0 #ffffff; - border: 1px solid #008cba; + background: none repeat scroll 0 0 var(--bs-body-bg); + border: 1px solid var(--bs-primary); padding: 14px; text-align: center; } diff --git a/app/assets/stylesheets/exercises.css.scss b/app/assets/stylesheets/exercises.css.scss index a2c0e8af..94a9a11c 100644 --- a/app/assets/stylesheets/exercises.css.scss +++ b/app/assets/stylesheets/exercises.css.scss @@ -1,5 +1,5 @@ code { - background-color: #F8F8F8 !important; + background-color: var(--bs-light-bg-subtle) !important; max-height: 100px; overflow: scroll; } @@ -14,7 +14,7 @@ input[type='file'] { .exercise { border-radius: 3px; - box-shadow: 0 2px 4px 0 rgba(0,0,0,0.2); + box-shadow: 0 2px 4px 0 rgba(var(--bs-black-rgb), 0.2); padding: 1px 10px 1px 10px; margin-bottom: 10px; @@ -50,15 +50,17 @@ input[type='file'] { // Graph Settings +text.axis, { + fill: var(--bs-body-color); +} + .axis path { fill: none; - stroke: #100; shape-rendering: crispEdges; } .axis line { fill: none; - stroke: #999; - //shape-rendering: crispEdges; + stroke: var(--bs-tertiary-color); } .y.axis path { @@ -77,29 +79,29 @@ input[type='file'] { } div#chart_1 { - background-color: #FAFAFA; + background-color: var(--bs-light-bg-subtle); } div#chart_2 { - background-color: #FAFAFA; + background-color: var(--bs-light-bg-subtle); } div#chart_stacked { max-height: 500px; - background-color: #FAFAFA; + background-color: var(--bs-light-bg-subtle); } a.file-heading { - color: black !important; + color: var(--bs-body-color) !important; text-decoration: none; } .bar { - fill: orange; + fill: var(--bs-warning); } .bar:hover { - fill: #ffd897; + fill: var(--bs-warning-border-subtle); } .container > form > .actions { @@ -110,8 +112,8 @@ a.file-heading { line-height: 1; font-weight: bold; padding: 12px; - background: rgba(0, 0, 0, 0.8); - color: #fff; + background: rgba(var(--bs-black-rgb), 0.8); + color: var(--bs-white); border-radius: 2px; } @@ -122,7 +124,7 @@ a.file-heading { font-size: 14px; width: 100%; line-height: 1; - color: rgba(0, 0, 0, 0.8); + color: rgba(var(--bs-black-rgb), 0.8); content: "\25BC"; position: absolute; text-align: center; @@ -142,7 +144,7 @@ a.file-heading { } .value { - border: 1px solid grey; + border: 1px solid var(--bs-border-color-translucent); padding: 10px; margin-bottom: 10px; } @@ -216,11 +218,11 @@ a.file-heading { } .export-success { - color: darkgreen; + color: var(--bs-success); font-size: 12pt; font-weight: 600; } .export-failure { - color: darkred; + color: var(--bs-danger); } diff --git a/app/assets/stylesheets/forms.css.scss b/app/assets/stylesheets/forms.css.scss index fd797034..f072c5e1 100644 --- a/app/assets/stylesheets/forms.css.scss +++ b/app/assets/stylesheets/forms.css.scss @@ -31,3 +31,7 @@ .toggle-input { font-size: 80%; } + +.wmd-preview { + background-color: var(--bs-secondary-bg); +} diff --git a/app/assets/stylesheets/request-for-comments.css.scss b/app/assets/stylesheets/request-for-comments.css.scss index 2b34915e..c8dadf92 100644 --- a/app/assets/stylesheets/request-for-comments.css.scss +++ b/app/assets/stylesheets/request-for-comments.css.scss @@ -1,7 +1,7 @@ .rfc { h5 { - color: #008CBA; + color: var(--bs-primary); } .text { @@ -25,8 +25,8 @@ .text { padding: 5px; - background-color: #FAFAFA; - border: 1px solid #CCCCCC; + background-color: var(--bs-light-bg-subtle); + border: 1px solid var(--bs-border-color-translucent); } } @@ -44,8 +44,8 @@ .text { padding: 5px; - background-color: #FAFAFA; - border: 1px solid #CCCCCC; + background-color: var(--bs-light-bg-subtle); + border: 1px solid var(--bs-border-color-translucent); } pre { @@ -77,26 +77,26 @@ .passed { border-radius: 50%; - background-color: #8efa00; - -webkit-box-shadow: 0 0 11px 1px rgba(44,222,0,1); - -moz-box-shadow: 0 0 11px 1px rgba(44,222,0,1); - box-shadow: 0 0 11px 1px rgba(44,222,0,1); + background-color: var(--bs-success); + -webkit-box-shadow: 0 0 11px 1px rgba(var(--bs-success-rgb), 1); + -moz-box-shadow: 0 0 11px 1px rgba(var(--bs-success-rgb), 1); + box-shadow: 0 0 11px 1px rgba(var(--bs-success-rgb), 1); } .unknown { border-radius: 50%; - background-color: #ffca00; - -webkit-box-shadow: 0 0 11px 1px rgb(255, 202, 0); - -moz-box-shadow: 0 0 11px 1px rgb(255, 202, 0); - box-shadow: 0 0 11px 1px rgb(255, 202, 0); + background-color: var(--bs-warning); + -webkit-box-shadow: 0 0 11px 1px rgba(var(--bs-warning-rgb), 1); + -moz-box-shadow: 0 0 11px 1px rgba(var(--bs-warning-rgb), 1); + box-shadow: 0 0 11px 1px rgba(var(--bs-warning-rgb), 1); } .failed { border-radius: 50%; - background-color: #ff2600; - -webkit-box-shadow: 0 0 11px 1px rgba(222,0,0,1); - -moz-box-shadow: 0 0 11px 1px rgba(222,0,0,1); - box-shadow: 0 0 11px 1px rgba(222,0,0,1); + background-color: var(--bs-danger); + -webkit-box-shadow: 0 0 11px 1px rgba(var(--bs-danger-rgb), 1); + -moz-box-shadow: 0 0 11px 1px rgba(var(--bs-danger-rgb), 1); + box-shadow: 0 0 11px 1px rgba(var(--bs-danger-rgb), 1); } } @@ -109,8 +109,8 @@ display: none; margin-top: 20px; padding: 5px; - border: solid lightgrey 1px; - background-color: rgba(20, 180, 20, 0.2); + border: solid var(--bs-border-color-translucent) 1px; + background-color: rgba(var(--bs-success-rgb),0.2); border-radius: 4px; button { @@ -127,7 +127,12 @@ #commentitor { margin-bottom: 2rem; height: 600px; - background-color:#f9f9f9 +} + +html[data-bs-theme="light"] { + #commentitor { + background-color: var(--bs-secondary-border-subtle); + } } :not(.allow_ace_tooltip) > .ace_tooltip { @@ -180,7 +185,7 @@ .comment-date { text-align: right; - color: #008cba; + color: var(--bs-primary); margin-left: 60%; font-size: x-small; } @@ -212,7 +217,7 @@ .comment-divider { width: 100%; height: 1px; - background-color: #008cba; + background-color: var(--bs-primary); overflow: hidden; margin-top: 10px; margin-bottom: 10px; @@ -226,7 +231,7 @@ .container { width: 100%; overflow-y: auto; - border: 1px solid #cccccc; + border: 1px solid var(--bs-border-color-translucent); padding: 15px; .comment-removed { @@ -264,14 +269,10 @@ input#subscribe { } .popover-footer { - color: #008cba; + color: var(--bs-primary); margin-top: 10px; } -.do-not-answer { - background-color: #ea2f1085; -} - #q_submission_study_group_id_in_chosen { margin-right: 20px; } diff --git a/app/assets/stylesheets/statistics.css.scss b/app/assets/stylesheets/statistics.css.scss index 40852ac4..65668595 100644 --- a/app/assets/stylesheets/statistics.css.scss +++ b/app/assets/stylesheets/statistics.css.scss @@ -36,51 +36,54 @@ div.unit-test-result { div.positive-result { border-radius: 50%; - background-color: #8efa00; - -webkit-box-shadow: 0px 0px 11px 1px rgba(44,222,0,1); - -moz-box-shadow: 0px 0px 11px 1px rgba(44,222,0,1); - box-shadow: 0px 0px 11px 1px rgba(44,222,0,1); + background-color: var(--bs-success); + -webkit-box-shadow: 0px 0px 11px 1px rgba(var(--bs-success-rgb), 1); + -moz-box-shadow: 0px 0px 11px 1px rgba(var(--bs-success-rgb), 1); + box-shadow: 0px 0px 11px 1px rgba(var(--bs-success-rgb), 1); } div.unknown-result { border-radius: 50%; - background-color: #ffca00; - -webkit-box-shadow: 0px 0px 11px 1px rgb(255, 202, 0); - -moz-box-shadow: 0px 0px 11px 1px rgb(255, 202, 0); - box-shadow: 0px 0px 11px 1px rgb(255, 202, 0); + background-color: var(--bs-warning); + -webkit-box-shadow: 0px 0px 11px 1px rgba(var(--bs-warning-rgb), 1); + -moz-box-shadow: 0px 0px 11px 1px rgba(var(--bs-warning-rgb), 1); + box-shadow: 0px 0px 11px 1px rgba(var(--bs-warning-rgb), 1); } div.negative-result { border-radius: 50%; - background-color: #ff2600; - -webkit-box-shadow: 0px 0px 11px 1px rgba(222,0,0,1); - -moz-box-shadow: 0px 0px 11px 1px rgba(222,0,0,1); - box-shadow: 0px 0px 11px 1px rgba(222,0,0,1); + background-color: var(--bs-danger); + -webkit-box-shadow: 0px 0px 11px 1px rgba(var(--bs-danger-rgb), 1); + -moz-box-shadow: 0px 0px 11px 1px rgba(var(--bs-danger-rgb), 1); + box-shadow: 0px 0px 11px 1px rgba(var(--bs-danger-rgb), 1); } -tr.active { - filter: brightness(85%); - color: #000000; +html[data-bs-theme="dark"] { + tr.active { + filter: brightness(175%); + } } -tr:not(.before_deadline,.within_grace_period,.after_late_deadline) { - background-color: #ffffff; +html[data-bs-theme="light"] { + tr.active { + filter: brightness(85%); + } } tr.highlight { - border-top: 2px solid rgba(222,0,0,1); + border-top: 2px solid var(--bs-red); } -.before_deadline { - background-color: #DAF7A6; +.before_deadline, .before_deadline > * { + background-color: var(--bs-success-bg-subtle) !important; } -.within_grace_period { - background-color: #F7DC6F; +.within_grace_period, .within_grace_period > * { + background-color: var(--bs-warning-bg-subtle) !important; } -.after_late_deadline { - background-color: #EC7063; +.after_late_deadline, .after_late_deadline > * { + background-color: var(--bs-danger-bg-subtle) !important; } ///////////////////////////////////////////////////////////////////////////////////////////// @@ -97,13 +100,13 @@ tr.highlight { grid-gap: 10px; > a { - color: #fff !important; + color: var(--bs-white) !important; text-decoration: none !important; > div { - border: 2px solid #0055ba; + border: 2px solid var(--bs-primary-text-emphasis); border-radius: 5px; - background-color: #008cba; + background-color: var(--bs-primary); padding: 1em; display: flex; flex-flow: column-reverse; diff --git a/app/javascript/application.js b/app/javascript/application.js index fc35b9cc..5310cb64 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -37,7 +37,9 @@ window.SentryUtils = { dynamicSamplingContextToSentryBaggageHeader, startIdleTra // CSS import 'chosen-js/chosen.css'; +import 'chosen-dark.scss'; import 'jstree/dist/themes/default/style.min.css'; +import 'jstree/dist/themes/default-dark/style.min.css'; // custom jquery-ui library for minimal mouse interaction support import 'jquery-ui/ui/widget' diff --git a/app/javascript/chosen-dark.scss b/app/javascript/chosen-dark.scss new file mode 100644 index 00000000..909f0f1f --- /dev/null +++ b/app/javascript/chosen-dark.scss @@ -0,0 +1,140 @@ +/*! +Chosen, a Select Box Enhancer for jQuery and Prototype +by Patrick Filler for Harvest, http://getharvest.com + +Version 1.7.0 +Full source at https://github.com/harvesthq/chosen +Copyright (c) 2011-2017 Harvest http://getharvest.com + +MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md +This file is generated by `grunt build`, do not edit it by hand. +*/ + +/* +Modified to dark version by Daniel Ziegler https://daniel-ziegler.com +MIT License +Full source at https://github.com/nook24/chosen-dark +*/ + +/* +Changed to work in conjunction with Bootstrap 5 for CodeOcean +*/ + +html[data-bs-theme="dark"] { + + .chosen-container .chosen-drop { + border-color: #333; + background: #212121; + box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15); + } + + + /* @end */ + /* @group Single Chosen */ + .chosen-container-single .chosen-single { + border-color: #333; + background: #212121; + background-image: linear-gradient(#353535 1%, #212121 15%); + box-shadow: 0 0 2px #5d5d5d inset, 0 1px 0 rgba(0, 0, 0, 0.05); + color: #e3e3e3; + } + + .chosen-container-single .chosen-default { + color: #999; + } + + .chosen-container-single .chosen-search input[type="text"] { + border-color: #333; + color: #e3e3e3; + } + + /* @end */ + /* @group Results */ + .chosen-container .chosen-results { + color: #e3e3e3; + } + + + .chosen-container .chosen-results li.disabled-result { + color: #505050; + } + + .chosen-container .chosen-results li.highlighted { + background-color: #3875d7; + background-image: linear-gradient(#3875d7 20%, #2a62bc 90%); + color: #fff; + } + + .chosen-container .chosen-results li.no-results { + color: #e3e3e3; + background: #1f1d1d; + } + + + /* @end */ + /* @group Multi Chosen */ + .chosen-container-multi .chosen-choices { + border-color: #333; + background: #212121; + background-image: linear-gradient(#353535 1%, #212121 15%); + } + + .chosen-container-multi .chosen-choices li.search-field input[type="text"] { + color: #e3e3e3; + } + + .chosen-container-multi .chosen-choices li.search-choice { + border-color: #000; + background-color: #212121; + background-image: linear-gradient(#353535 1%, #212121 15%); + box-shadow: 0 0 2px #5d5d5d inset, 0 1px 0 rgba(0, 0, 0, 0.05); + color: #e3e3e3; + } + + + .chosen-container-multi .chosen-choices li.search-choice-disabled { + border: 1px solid #ccc; + background-color: #e4e4e4; + background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + color: #666; + } + + .chosen-container-multi .chosen-choices li.search-choice-focus { + background: #d4d4d4; + } + + + .chosen-container-multi .chosen-drop .result-selected { + color: #505050; + } + + /* @end */ + /* @group Active */ + .chosen-container-active .chosen-single { + border: 1px solid #5897fb; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + } + + .chosen-container-active.chosen-with-drop .chosen-single { + border-color: #333; + background-image: linear-gradient(#353535 1%, #212121 15%); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.3) inset; + } + + + .chosen-container-active .chosen-choices { + border: 1px solid #5897fb; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); + } + + .chosen-container-active .chosen-choices li.search-field input[type="text"] { + color: #e3e3e3 !important; + } + + /* @end */ + /* @group Disabled Support */ + .chosen-disabled { + opacity: 0.5 !important; + } + /* @end */ +} diff --git a/app/javascript/highlight.js b/app/javascript/highlight.js index 375a92e1..9ee2a8b6 100644 --- a/app/javascript/highlight.js +++ b/app/javascript/highlight.js @@ -5,6 +5,3 @@ import hljs from 'highlight.js/lib/common' import julia from 'highlight.js/lib/languages/julia'; hljs.registerLanguage('julia', julia); window.hljs = hljs; - -// CSS -import 'highlight.js/styles/base16/tomorrow.css' diff --git a/app/javascript/highlight.scss b/app/javascript/highlight.scss new file mode 100644 index 00000000..82e8a328 --- /dev/null +++ b/app/javascript/highlight.scss @@ -0,0 +1,9 @@ +// CSS for highlight.js + +html[data-bs-theme="light"] { + @import 'highlight.js/styles/base16/tomorrow'; +} + +html[data-bs-theme="dark"] { + @import 'highlight.js/styles/base16/tomorrow-night'; +} diff --git a/app/javascript/stylesheets.scss b/app/javascript/stylesheets.scss index 5a8fc14a..a57349e6 100644 --- a/app/javascript/stylesheets.scss +++ b/app/javascript/stylesheets.scss @@ -11,6 +11,29 @@ $web-font-path: '//'; @import '~bootswatch/dist/yeti/variables'; @import '~bootstrap/scss/bootstrap'; @import '~bootswatch/dist/yeti/bootswatch'; + +// We define our own button style here, since `btn-outline-dark` and `btn-outline-light` do not switch colors. +html[data-bs-theme="dark"] { + .btn-outline-contrast { + @extend .btn-outline-light; + } + + .bg-contrast { + @extend .bg-light; + } +} + + +html[data-bs-theme="light"] { + .btn-outline-contrast { + @extend .btn-outline-dark; + } + + .bg-contrast { + @extend .bg-dark; + } +} + $fa-font-path: '~@fortawesome/fontawesome-free/webfonts/'; @import '~@fortawesome/fontawesome-free/scss/fontawesome'; @import '~@fortawesome/fontawesome-free/scss/solid'; diff --git a/app/javascript/vis.js b/app/javascript/vis.js index 8e81ed90..5dee8d25 100644 --- a/app/javascript/vis.js +++ b/app/javascript/vis.js @@ -3,6 +3,3 @@ // JS import * as vis from 'vis'; window.vis = vis; - -// CSS -import 'vis-timeline/dist/vis-timeline-graph2d.css'; diff --git a/app/javascript/vis.scss b/app/javascript/vis.scss new file mode 100644 index 00000000..0b157803 --- /dev/null +++ b/app/javascript/vis.scss @@ -0,0 +1,19 @@ +// CSS + +@import 'vis-timeline/dist/vis-timeline-graph2d'; + +html[data-bs-theme="dark"] { + // Color overwrites for dark mode + + .vis-legend { + background-color: rgba(var(--bs-secondary-bg-rgb), 0.65) !important; + } + + .vis-timeline .vis-outline { + fill: var(--bs-body-bg) !important; + } + + .vis-data-axis .vis-major, .vis-time-axis .vis-text { + color: var(--bs-secondary-text-emphasis) !important; + } +} diff --git a/app/views/application/_breadcrumbs_and_title.html.slim b/app/views/application/_breadcrumbs_and_title.html.slim index ff5d1671..730a54b1 100644 --- a/app/views/application/_breadcrumbs_and_title.html.slim +++ b/app/views/application/_breadcrumbs_and_title.html.slim @@ -20,7 +20,7 @@ - title = "#{active_action} - #{application_name}" - content_for :breadcrumbs do .container.mb-4 - ul.breadcrumb.bg-light.px-3.py-2 + ul.breadcrumb.bg-body-secondary.px-3.py-2 - if root_element.present? li.breadcrumb-item.small = root_element diff --git a/app/views/application/_color_mode_selector.html.slim b/app/views/application/_color_mode_selector.html.slim new file mode 100644 index 00000000..8b93e512 --- /dev/null +++ b/app/views/application/_color_mode_selector.html.slim @@ -0,0 +1,17 @@ +li.nav-item.dropdown + a.nav-link.dropdown-toggle.me-3 data-bs-toggle='dropdown' href='#' + = t('shared.color_mode.title') + span.caret + ul.dropdown-menu.p-0.mt-1 role='menu' + li + button.dropdown-item.d-flex.align-items-center data={ 'bs-theme-value': 'light' } + i.fa-fw.fa-solid.fa-sun + = t('shared.color_mode.light') + li + button.dropdown-item.d-flex.align-items-center data={ 'bs-theme-value': 'dark' } + i.fa-fw.fa-solid.fa-moon + = t('shared.color_mode.dark') + li + button.dropdown-item.d-flex.align-items-center data={ 'bs-theme-value': 'auto' } + i.fa-fw.fa-solid.fa-wand-magic-sparkles + = t('shared.color_mode.auto') diff --git a/app/views/application/_locale_selector.html.slim b/app/views/application/_locale_selector.html.slim index 51a41677..a6332186 100644 --- a/app/views/application/_locale_selector.html.slim +++ b/app/views/application/_locale_selector.html.slim @@ -1,5 +1,5 @@ li.nav-item.dropdown - a.nav-link.dropdown-toggle.mx-3 data-bs-toggle='dropdown' href='#' + a.nav-link.dropdown-toggle.me-3 data-bs-toggle='dropdown' href='#' = t("locales.#{I18n.locale}") span.caret ul.dropdown-menu.p-0.mt-1 role='menu' diff --git a/app/views/community_solutions/_form.html.slim b/app/views/community_solutions/_form.html.slim index 480bf004..7f59b4f2 100644 --- a/app/views/community_solutions/_form.html.slim +++ b/app/views/community_solutions/_form.html.slim @@ -42,7 +42,7 @@ = render('exercises/editor_frame', exercise: @community_solution.exercise, file: file) .col-xl-6.container-fluid - div.bg-dark.h-100.float-start.row style="width: 1px" + div.bg-contrast.h-100.float-start.row style="width: 1px" div h4 = t('community_solutions.your_submission') diff --git a/app/views/consumers/show.html.slim b/app/views/consumers/show.html.slim index c5acac9d..b5a3a12e 100644 --- a/app/views/consumers/show.html.slim +++ b/app/views/consumers/show.html.slim @@ -5,7 +5,7 @@ h1 = row(label: 'consumer.name', value: @consumer.name) - %w[oauth_key oauth_secret].each do |attribute| = row(label: "consumer.#{attribute}") do - = content_tag(:input, nil, class: 'form-control bg-secondary', readonly: true, value: @consumer.send(attribute)) + = content_tag(:input, nil, class: 'form-control bg-body-secondary', readonly: true, value: @consumer.send(attribute)) = row(label: 'consumer.rfc_visibility', value: t("activerecord.attributes.consumer.rfc_visibility_type.#{@consumer.rfc_visibility}")) = render('study_groups/table', study_groups: @consumer.study_groups.sort) diff --git a/app/views/execution_environments/show.html.slim b/app/views/execution_environments/show.html.slim index 53537db0..31b506b6 100644 --- a/app/views/execution_environments/show.html.slim +++ b/app/views/execution_environments/show.html.slim @@ -3,10 +3,10 @@ h1.d-inline-block = @execution_environment = render('shared/edit_button', object: @execution_environment) button.btn.btn-secondary.float-end.dropdown-toggle data-bs-toggle='dropdown' type='button' ul.dropdown-menu.dropdown-menu-end role='menu' - li = link_to(t('execution_environments.index.synchronize.button'), sync_to_runner_management_execution_environment_path(@execution_environment), method: :post, class: 'dropdown-item text-dark') if policy(@execution_environment).sync_to_runner_management? - li = link_to(t('execution_environments.index.shell'), shell_execution_environment_path(@execution_environment), class: 'dropdown-item text-dark') if policy(@execution_environment).shell? - li = link_to(t('shared.statistics'), statistics_execution_environment_path(@execution_environment), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@execution_environment).statistics? - li = link_to(t('shared.destroy'), @execution_environment, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@execution_environment).destroy? + li = link_to(t('execution_environments.index.synchronize.button'), sync_to_runner_management_execution_environment_path(@execution_environment), method: :post, class: 'dropdown-item') if policy(@execution_environment).sync_to_runner_management? + li = link_to(t('execution_environments.index.shell'), shell_execution_environment_path(@execution_environment), class: 'dropdown-item') if policy(@execution_environment).shell? + li = link_to(t('shared.statistics'), statistics_execution_environment_path(@execution_environment), 'data-turbolinks' => "false", class: 'dropdown-item') if policy(@execution_environment).statistics? + li = link_to(t('shared.destroy'), @execution_environment, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item') if policy(@execution_environment).destroy? = row(label: 'execution_environment.name', value: @execution_environment.name) = row(label: 'execution_environment.user', value: link_to_if(policy(@execution_environment.author).show?, @execution_environment.author, @execution_environment.author)) diff --git a/app/views/exercises/_editor.html.slim b/app/views/exercises/_editor.html.slim index dfd3f477..8143e873 100644 --- a/app/views/exercises/_editor.html.slim +++ b/app/views/exercises/_editor.html.slim @@ -35,8 +35,8 @@ #statusbar.d-flex.justify-content-between div - - if !@embed_options[:disable_download] && @exercise.hide_file_tree? - button#download.p-0.border-0.btn-link.visible.bg-white.text-primary + - if !@embed_options[:disable_download] && !@exercise.hide_file_tree? + button#download.p-0.border-0.btn-link.visible.bg-body.text-primary i.fa-solid.fa-arrow-down = t('exercises.editor.download') @@ -47,7 +47,7 @@ = " | " - button#start-over-active-file.p-0.border-0.btn-link.bg-white.text-primary data-message-confirm=t('exercises.editor.confirm_start_over_active_file') data-url=reload_exercise_path(@exercise) + button#start-over-active-file.p-0.border-0.btn-link.bg-body.text-primary data-message-confirm=t('exercises.editor.confirm_start_over_active_file') data-url=reload_exercise_path(@exercise) i.fa-solid.fa-circle-notch.fa-spin.d-none i.fa-solid.fa-clock-rotate-left = t('exercises.editor.start_over_active_file') diff --git a/app/views/exercises/_editor_file_tree.html.slim b/app/views/exercises/_editor_file_tree.html.slim index f9d83300..34fc202b 100644 --- a/app/views/exercises/_editor_file_tree.html.slim +++ b/app/views/exercises/_editor_file_tree.html.slim @@ -1,11 +1,11 @@ div.d-grid.gap-2 id='sidebar-collapsed' class=(@exercise.hide_file_tree && @tips.blank? ? '' : 'd-none') - = render('editor_button', classes: 'btn-outline-dark', data: {:'data-bs-toggle' => 'tooltip', :'data-bs-placement' => 'right'}, icon: 'fa-solid fa-square-plus', id: 'sidebar-collapse-collapsed', label:'', title:t('exercises.editor.expand_action_sidebar')) + = render('editor_button', classes: 'btn-outline-contrast', data: {:'data-bs-toggle' => 'tooltip', :'data-bs-placement' => 'right'}, icon: 'fa-solid fa-square-plus', id: 'sidebar-collapse-collapsed', label:'', title:t('exercises.editor.expand_action_sidebar')) - unless @embed_options[:disable_hints] or @tips.blank? = render('editor_button', classes: 'btn-secondary btn mb-4', data: {:'data-bs-toggle' => 'tooltip', :'data-bs-placement' => 'right'}, icon: 'fa-solid fa-lightbulb', id: 'tips-collapsed', label:'', title: t('exercises.form.tips')) div.d-grid.enforce-bottom-margin id='sidebar-uncollapsed' class=(@exercise.hide_file_tree && @tips.blank? ? 'd-none' : '') - = render('editor_button', classes: 'btn-outline-dark overflow-hidden mb-2', icon: 'fa-solid fa-square-minus', id: 'sidebar-collapse', label: t('exercises.editor.collapse_action_sidebar')) + = render('editor_button', classes: 'btn-outline-contrast overflow-hidden mb-2', icon: 'fa-solid fa-square-minus', id: 'sidebar-collapse', label: t('exercises.editor.collapse_action_sidebar')) #content-left-sidebar.overflow-scroll - unless @exercise.hide_file_tree div.overflow-scroll diff --git a/app/views/exercises/_editor_output.html.slim b/app/views/exercises/_editor_output.html.slim index 0d4b3bd6..d7c7ac47 100644 --- a/app/views/exercises/_editor_output.html.slim +++ b/app/views/exercises/_editor_output.html.slim @@ -1,7 +1,7 @@ div.d-grid id='output_sidebar_collapsed' - = render('editor_button', classes: 'btn-outline-dark btn', data: {:'data-bs-toggle' => 'tooltip', :'data-bs-placement' => 'left'}, title: t('exercises.editor.expand_output_sidebar'), icon: 'fa-solid fa-square-plus', id: 'toggle-sidebar-output-collapsed', label: '') + = render('editor_button', classes: 'btn-outline-contrast btn', data: {:'data-bs-toggle' => 'tooltip', :'data-bs-placement' => 'left'}, title: t('exercises.editor.expand_output_sidebar'), icon: 'fa-solid fa-square-plus', id: 'toggle-sidebar-output-collapsed', label: '') div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom-margin' data-message-no-output=t('exercises.implement.no_output_yet') - = render('editor_button', classes: 'btn-outline-dark btn overflow-hidden mb-2', icon: 'fa-solid fa-square-minus', id: 'toggle-sidebar-output', label: t('exercises.editor.collapse_output_sidebar')) + = render('editor_button', classes: 'btn-outline-contrast btn overflow-hidden mb-2', icon: 'fa-solid fa-square-minus', id: 'toggle-sidebar-output', label: t('exercises.editor.collapse_output_sidebar')) #content-right-sidebar.overflow-scroll @@ -17,7 +17,7 @@ div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-botto li.card.mt-2 .card-header.py-2 h5.card-title.m-0 == t('exercises.implement.test_file', filename: '', number: 0) - .card-body.bg-white.text-dark + .card-body = row(label: 'exercises.implement.passed_tests') do span.number | 0 @@ -37,7 +37,7 @@ div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-botto li.card.mt-2 .card-header.py-2 h5.card-title.m-0 == t('exercises.implement.linter_file', filename: '', number: 0) - .card-body.bg-white.text-dark + .card-body = row(label: 'exercises.implement.code_rating') do span.number | 0 @@ -84,6 +84,6 @@ div.d-grid id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-botto #output .output-element.overflow-scroll = t('exercises.implement.no_output_yet') - if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] && !@embed_options[:disable_hints] && !@embed_options[:hide_test_results] - #flowrHint.mb-2.card.text-white.bg-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab' - .card-header = t('exercises.implement.flowr.heading') - .card-body.text-dark.bg-white + #flowrHint.mb-2.card data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab' + .card-header.text-white.bg-info = t('exercises.implement.flowr.heading') + .card-body diff --git a/app/views/exercises/_tips_content.html.slim b/app/views/exercises/_tips_content.html.slim index cefd7fbb..e3070e5f 100644 --- a/app/views/exercises/_tips_content.html.slim +++ b/app/views/exercises/_tips_content.html.slim @@ -9,5 +9,5 @@ .card-header.py-2 i.fa-solid.fa-lightbulb = t('exercises.implement.tips.heading') - .card-body.text-dark.bg-white.p-2 + .card-body.p-2 = render(partial: 'tips/collapsed_card', collection: @tips, as: :exercise_tip, locals: { tip_prefix: '' }) diff --git a/app/views/exercises/external_users/statistics.html.slim b/app/views/exercises/external_users/statistics.html.slim index 3fedb649..d6628369 100644 --- a/app/views/exercises/external_users/statistics.html.slim +++ b/app/views/exercises/external_users/statistics.html.slim @@ -40,17 +40,17 @@ h1 =index - index += 1 - if policy(@exercise).detailed_statistics? - .bg-light.w-100.p-2.mb-4.align-items-center.d-flex.justify-content-between + .bg-body-secondary.w-100.p-2.mb-4.align-items-center.d-flex.justify-content-between - if @show_autosaves span.ps-1.pb-1 i.fa-solid.fa-circle-info.align-middle small.me-5.ms-1 = t('.toggle_status_on') - = link_to t('.toggle_autosave_off'), statistics_external_user_exercise_path(show_autosaves: false), class: "btn btn-outline-dark float-end btn-sm" + = link_to t('.toggle_autosave_off'), statistics_external_user_exercise_path(show_autosaves: false), class: "btn btn-outline-contrast float-end btn-sm" - else span.ps-1.pb-1 i.fa-solid.fa-circle-info.align-middle small.me-5.ms-1 = t('.toggle_status_off') - = link_to t('.toggle_autosave_on'), statistics_external_user_exercise_path(show_autosaves: true), class: "btn btn-outline-dark float-end btn-sm" + = link_to t('.toggle_autosave_on'), statistics_external_user_exercise_path(show_autosaves: true), class: "btn btn-outline-contrast float-end btn-sm" #timeline .table-responsive table.table diff --git a/app/views/exercises/show.html.slim b/app/views/exercises/show.html.slim index f8762ed0..bdf2a95e 100644 --- a/app/views/exercises/show.html.slim +++ b/app/views/exercises/show.html.slim @@ -10,13 +10,13 @@ h1.d-inline-block = @exercise = render('shared/edit_button', object: @exercise) button.btn.btn-secondary.float-end.dropdown-toggle data-bs-toggle='dropdown' type='button' ul.dropdown-menu.dropdown-menu-end role='menu' - li = link_to(t('exercises.index.implement'), implement_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).implement? - li = link_to(t('shared.statistics'), statistics_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item text-dark') if policy(@exercise).statistics? - li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).feedback? - li = link_to(t('activerecord.models.request_for_comment.other'), rfcs_for_exercise_path(@exercise), class: 'dropdown-item text-dark') if policy(@exercise).rfcs_for_exercise? - li = link_to(t('shared.destroy'), @exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item text-dark') if policy(@exercise).destroy? - li = link_to(t('exercises.index.clone'), clone_exercise_path(@exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item text-dark') if policy(@exercise).clone? - li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start text-dark', data: {'exercise-id' => @exercise.id}) if policy(@exercise).export_external_confirm? + li = link_to(t('exercises.index.implement'), implement_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item') if policy(@exercise).implement? + li = link_to(t('shared.statistics'), statistics_exercise_path(@exercise), 'data-turbolinks' => "false", class: 'dropdown-item') if policy(@exercise).statistics? + li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(@exercise), class: 'dropdown-item') if policy(@exercise).feedback? + li = link_to(t('activerecord.models.request_for_comment.other'), rfcs_for_exercise_path(@exercise), class: 'dropdown-item') if policy(@exercise).rfcs_for_exercise? + li = link_to(t('shared.destroy'), @exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item') if policy(@exercise).destroy? + li = link_to(t('exercises.index.clone'), clone_exercise_path(@exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item') if policy(@exercise).clone? + li = link_to(t('exercises.export_codeharbor.label'), '', class: 'dropdown-item export-start', data: {'exercise-id' => @exercise.id}) if policy(@exercise).export_external_confirm? = row(label: 'exercise.title', value: @exercise.title) = row(label: 'exercise.user', value: link_to_if(policy(@exercise.author).show?, @exercise.author, @exercise.author)) @@ -35,7 +35,7 @@ h1.d-inline-block = @exercise = row(label: 'exercise.uuid', value: @exercise.uuid) = row(label: 'exercise.tags', value: @exercise.exercise_tags.map{|et| "#{et.tag.name} (#{et.factor})"}.sort.join(", ")) = row(label: 'exercise.embedding_parameters', class: 'mb-4') do - = content_tag(:input, nil, class: 'form-control bg-secondary mb-4', readonly: true, value: @exercise.unpublished? ? t('exercises.show.is_unpublished') : embedding_parameters(@exercise)) + = content_tag(:input, nil, class: 'form-control bg-body-secondary mb-4', readonly: true, value: @exercise.unpublished? ? t('exercises.show.is_unpublished') : embedding_parameters(@exercise)) - unless @tips.blank? .mt-2 diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index a192572f..d50194a8 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -81,10 +81,10 @@ h1 = @exercise - latest_user_submission = submissions.where(user: user).final.latest - if latest_user_submission.present? - if latest_user_submission.before_deadline? - .unit-test-result.positive-result.before_deadline + .unit-test-result.positive-result - elsif latest_user_submission.within_grace_period? - .unit-test-result.unknown-result.within_grace_period + .unit-test-result.unknown-result - elsif latest_user_submission.after_late_deadline? - .unit-test-result.negative-result.after_late_deadline + .unit-test-result.negative-result td = us['runs'] if policy(@exercise).detailed_statistics? td = @exercise.average_working_time_for(user) or 0 if policy(@exercise).detailed_statistics? diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index 5174f9ed..a8ddade3 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -29,6 +29,7 @@ html lang="#{I18n.locale || I18n.default_locale}" data-default-locale="#{I18n.de #navbar-collapse.collapse.navbar-collapse = render('navigation', cached: true) ul.nav.navbar-nav.ms-auto + = render('color_mode_selector', cached: true) = render('locale_selector', cached: true) li.nav-item.me-3 = link_to(t('shared.help.link'), '#modal-help', data: {'bs-toggle': 'modal'}, class: 'nav-link') = render('session') diff --git a/app/views/proxy_exercises/show.html.slim b/app/views/proxy_exercises/show.html.slim index e740bbe0..275a3db2 100644 --- a/app/views/proxy_exercises/show.html.slim +++ b/app/views/proxy_exercises/show.html.slim @@ -9,7 +9,7 @@ h1 = row(label: 'exercise.public', value: @proxy_exercise.public?) = row(label: 'exercise.description', value: @proxy_exercise.description) = row(label: 'exercise.embedding_parameters', class: 'mb-4') do - = content_tag(:input, nil, class: 'form-control bg-secondary mb-4', readonly: true, value: embedding_parameters(@proxy_exercise)) + = content_tag(:input, nil, class: 'form-control bg-body-secondary mb-4', readonly: true, value: embedding_parameters(@proxy_exercise)) h2.mt-4 Exercises .table-responsive diff --git a/app/views/request_for_comments/_list_entry.html.slim b/app/views/request_for_comments/_list_entry.html.slim index 72984276..7237bc37 100644 --- a/app/views/request_for_comments/_list_entry.html.slim +++ b/app/views/request_for_comments/_list_entry.html.slim @@ -3,7 +3,7 @@ tr.table-row-clickable data-id=request_for_comment.id data-href=request_for_comm - if request_for_comment.solved? span.fa-solid.fa-check.fa-2x.text-success aria-hidden="true" - elsif request_for_comment.full_score_reached - span.fa-solid.fa-check.fa-2x style="color:darkgrey" aria-hidden="true" + span.fa-solid.fa-check.fa-2x style="color: var(--bs-secondary-text-emphasis);" aria-hidden="true" - else = '' td.text-center = request_for_comment.comments_count diff --git a/app/views/request_for_comments/index.html.slim b/app/views/request_for_comments/index.html.slim index bade3637..929d34e2 100644 --- a/app/views/request_for_comments/index.html.slim +++ b/app/views/request_for_comments/index.html.slim @@ -37,7 +37,7 @@ h1 = RequestForComment.model_name.human(count: 2) span class="fa-solid fa-check" aria-hidden="true" - elsif request_for_comment.full_score_reached td - span class="fa-solid fa-check" style="color:darkgrey" aria-hidden="true" + span class="fa-solid fa-check" style="color: var(--bs-secondary-text-emphasis);" aria-hidden="true" - else td = '' td = link_to_if(policy(request_for_comment).show?, request_for_comment.submission.exercise.title, request_for_comment) diff --git a/app/views/request_for_comments/show.html.slim b/app/views/request_for_comments/show.html.slim index f39dff79..69e075e9 100644 --- a/app/views/request_for_comments/show.html.slim +++ b/app/views/request_for_comments/show.html.slim @@ -1,7 +1,7 @@ .list-group h4#exercise_caption.list-group-item-heading data-exercise-id="#{@request_for_comment.exercise.id}" data-rfc-id="#{@request_for_comment.id}" - if @request_for_comment.solved? - span.fa-solid.fa-check aria-hidden="true" + span.fa-solid.fa-check.me-2 aria-hidden="true" = link_to_if(policy(@request_for_comment.exercise).show?, @request_for_comment.exercise.title, [:implement, @request_for_comment.exercise]) p.list-group-item-text - user = @request_for_comment.user diff --git a/app/views/shared/_form_filters.html.slim b/app/views/shared/_form_filters.html.slim index 533a2c82..0dfcc19d 100644 --- a/app/views/shared/_form_filters.html.slim +++ b/app/views/shared/_form_filters.html.slim @@ -1,4 +1,4 @@ -.card.card-body.bg-light.flex-row.container.justify-content-center +.card.card-body.bg-body-secondary.flex-row.container.justify-content-center = search_form_for(@search, class: 'clearfix filter-form align-items-center row g-2 w-100') do |f| = yield(f) .col-sm.ge-4.gy-2 diff --git a/config/locales/de.yml b/config/locales/de.yml index 2d35ee4c..9eb33370 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -817,7 +817,7 @@ de: runtime_output: "Programmausgabe" test_results: "Testergebnisse" sessions: - expired: Ihre Session ist abgelaufen. Bitte laden Sie diese Seite neu bevor Sie fortfahren. + expired: Ihre Session ist abgelaufen. Bitte laden Sie diese Seite neu bevor Sie fortfahren. create: failure: Fehlerhafte E-Mail oder Passwort. success: Sie haben sich erfolgreich angemeldet. @@ -859,6 +859,11 @@ de: confirm_destroy: Sind Sie sicher? create: '%{model} erstellen' created_at: Erstellt + color_mode: + title: Erscheinungsbild + light: Hell + dark: Dunkel + auto: Automatisch destroy: Löschen edit: Bearbeiten actions_button: 'Andere Aktionen' @@ -873,7 +878,7 @@ de: privacy_policy: Datenschutzerklärung index: Index message_failure: Leider ist ein Fehler auf unserer Plattform aufgetreten. Bitte probieren Sie es später noch einmal. - websocket_failure: Leider ist ein Verbindungsproblem aufgetreten. Bitte überprüfen Sie Websocket-Verbindungen mit diesem Tool und versuchen Sie es erneut. + websocket_failure: Leider ist ein Verbindungsproblem aufgetreten. Bitte überprüfen Sie Websocket-Verbindungen mit diesem Tool und versuchen Sie es erneut. new: Hinzufügen new_model: '%{model} hinzufügen' number: Nummer diff --git a/config/locales/en.yml b/config/locales/en.yml index 78495acd..b31f12f3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -817,7 +817,7 @@ en: runtime_output: "Runtime Output" test_results: "Test Results" sessions: - expired: Your session has expired. Please reload this page before continuing. + expired: Your session has expired. Please reload this page before continuing. create: failure: Invalid email or password. success: Successfully signed in. @@ -859,6 +859,11 @@ en: confirm_destroy: Are you sure? create: 'Create %{model}' created_at: Created At + color_mode: + title: Appearance + light: Light + dark: Dark + auto: Auto destroy: Delete edit: Edit actions_button: 'Other actions' @@ -873,7 +878,7 @@ en: privacy_policy: Privacy Policy index: Index message_failure: 'Sorry, something went wrong.' - websocket_failure: Sorry, a connection issue occoured. Please check WebSocket connections with this tool and try again. + websocket_failure: Sorry, a connection issue occoured. Please check WebSocket connections with this tool and try again. new: Add new_model: 'Add %{model}' number: Number diff --git a/lib/assets/javascripts/color_mode_picker.js b/lib/assets/javascripts/color_mode_picker.js new file mode 100644 index 00000000..74674877 --- /dev/null +++ b/lib/assets/javascripts/color_mode_picker.js @@ -0,0 +1,95 @@ +/*! + * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under the Creative Commons Attribution 3.0 Unported License. + * Taken from https://getbootstrap.com/docs/5.3/customize/color-modes/#javascript + */ + +const getStoredTheme = () => localStorage.getItem('theme') +const setStoredTheme = theme => localStorage.setItem('theme', theme) + +const getPreferredTheme = () => { + const storedTheme = getStoredTheme() + if (storedTheme) { + return storedTheme + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' +} + +const setTheme = theme => { + let currentTheme = theme || 'auto'; + if (theme === 'auto') { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + currentTheme = 'dark' + } else { + currentTheme = 'light' + } + } + + const event = new CustomEvent('theme:change', { + bubbles: true, + cancelable: false, + detail: { + preferredTheme: theme, + currentTheme: currentTheme, + } + }) + document.dispatchEvent(event) + document.documentElement.setAttribute('data-bs-theme', currentTheme) +} + +window.getCurrentTheme = () => { + return document.documentElement.getAttribute('data-bs-theme'); +} + +const showActiveTheme = (theme, focus = false) => { + const themeSwitcher = document.querySelector('#bd-theme') + + if (!themeSwitcher) { + return + } + + const themeSwitcherText = document.querySelector('#bd-theme-text') + const activeThemeIcon = document.querySelector('.theme-icon-active use') + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) + const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href') + + document.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active') + element.setAttribute('aria-pressed', 'false') + }) + + btnToActive.classList.add('active') + btnToActive.setAttribute('aria-pressed', 'true') + activeThemeIcon.setAttribute('href', svgOfActiveBtn) + const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})` + themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) + + if (focus) { + themeSwitcher.focus() + } +} + +$(document).on('turbolinks:load', function() { + setTheme(getPreferredTheme()) + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const storedTheme = getStoredTheme() + if (storedTheme !== 'light' && storedTheme !== 'dark') { + setTheme(getPreferredTheme()) + } + }) + + showActiveTheme(getPreferredTheme()) + + document.querySelectorAll('[data-bs-theme-value]') + .forEach(toggle => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value') + setStoredTheme(theme) + setTheme(theme) + showActiveTheme(theme, true) + }) + }) +})