Add CommunitySolution
* Also slightly refactor some JS files
This commit is contained in:
27
app/assets/javascripts/community_solution.js
Normal file
27
app/assets/javascripts/community_solution.js
Normal file
@ -0,0 +1,27 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
|
||||
if ($.isController('community_solutions') && $('#community-solution-editor').isPresent()) {
|
||||
CodeOceanEditor.sendEvents = false;
|
||||
CodeOceanEditor.editors = [];
|
||||
CodeOceanEditor.initializeDescriptionToggle();
|
||||
CodeOceanEditor.configureEditors();
|
||||
CodeOceanEditor.initializeEditors();
|
||||
CodeOceanEditor.initializeEditors(true);
|
||||
CodeOceanEditor.initializeFileTree();
|
||||
CodeOceanEditor.initializeFileTree(true);
|
||||
CodeOceanEditor.showFirstFile();
|
||||
CodeOceanEditor.showFirstFile(true);
|
||||
CodeOceanEditor.resizeAceEditors();
|
||||
CodeOceanEditor.resizeAceEditors(true);
|
||||
|
||||
$.extend(
|
||||
CodeOceanEditor,
|
||||
CodeOceanEditorAJAX,
|
||||
CodeOceanEditorSubmissions
|
||||
)
|
||||
|
||||
$('#submit').one('click', CodeOceanEditorSubmissions.submitCode.bind(CodeOceanEditor));
|
||||
$('#accept').one('click', CodeOceanEditorSubmissions.submitCode.bind(CodeOceanEditor));
|
||||
}
|
||||
|
||||
});
|
@ -15,7 +15,7 @@ var CodeOceanEditor = {
|
||||
ENTER_KEY_CODE: 13,
|
||||
|
||||
//Request-For-Comments-Configuration
|
||||
REQUEST_FOR_COMMENTS_DELAY: 3 * 60 * 1000,
|
||||
REQUEST_FOR_COMMENTS_DELAY: 0,
|
||||
REQUEST_TOOLTIP_TIME: 5000,
|
||||
|
||||
editors: [],
|
||||
@ -140,20 +140,37 @@ var CodeOceanEditor = {
|
||||
}
|
||||
},
|
||||
|
||||
showFirstFile: function () {
|
||||
var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first();
|
||||
var file_id = frame.find('.editor').data('file-id');
|
||||
showFirstFile: function (own_solution = false) {
|
||||
let frame;
|
||||
let filetree;
|
||||
let editorSelector;
|
||||
if (own_solution) {
|
||||
frame = $('.own-frame[data-role="main_file"]').isPresent() ? $('.own-frame[data-role="main_file"]') : $('.own-frame').first();
|
||||
filetree = $('#own-files');
|
||||
editorSelector = '.own-editor';
|
||||
} else {
|
||||
frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first();
|
||||
filetree = $('#files');
|
||||
editorSelector = '.editor';
|
||||
}
|
||||
|
||||
var file_id = frame.find(editorSelector).data('file-id');
|
||||
this.setActiveFile(frame.data('filename'), file_id);
|
||||
var filetree = $('#files');
|
||||
this.selectFileInJsTree(filetree, file_id);
|
||||
this.showFrame(frame);
|
||||
this.toggleButtonStates();
|
||||
},
|
||||
|
||||
showFrame: function (frame) {
|
||||
if (frame.hasClass('own-frame')) {
|
||||
$('.own-frame').hide();
|
||||
} else {
|
||||
$('.frame').hide();
|
||||
}
|
||||
|
||||
this.active_frame = frame;
|
||||
$('.frame').hide();
|
||||
frame.show();
|
||||
this.resizeParentOfAceEditor(frame.find('.ace_editor.ace-tm'));
|
||||
},
|
||||
|
||||
getProgressBarClass: function (percentage) {
|
||||
@ -203,8 +220,15 @@ var CodeOceanEditor = {
|
||||
},
|
||||
|
||||
|
||||
resizeAceEditors: function () {
|
||||
$('.editor').each(function (index, element) {
|
||||
resizeAceEditors: function (own_solution = false) {
|
||||
let editorSelector;
|
||||
if (own_solution) {
|
||||
editorSelector = $('.own-editor')
|
||||
} else {
|
||||
editorSelector = $('.editor')
|
||||
}
|
||||
|
||||
editorSelector.each(function (index, element) {
|
||||
this.resizeParentOfAceEditor(element);
|
||||
}.bind(this));
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
@ -212,13 +236,21 @@ var CodeOceanEditor = {
|
||||
|
||||
resizeParentOfAceEditor: function (element) {
|
||||
// calculate needed size: window height - position of top of ACE editor - height of autosave label below editor - 5 for bar margins
|
||||
var windowHeight = window.innerHeight - $(element).offset().top - $('#statusbar').height() - 5;
|
||||
var windowHeight = window.innerHeight - $(element).offset().top - ($('#statusbar').height() || 0) - 5;
|
||||
$(element).parent().height(windowHeight);
|
||||
},
|
||||
|
||||
initializeEditors: function () {
|
||||
this.editors = [];
|
||||
$('.editor').each(function (index, element) {
|
||||
initializeEditors: function (own_solution = false) {
|
||||
// Initialize the editors array if not present already. This is mainly required for community solutions
|
||||
this.editors = this.editors || [];
|
||||
let editorSelector;
|
||||
if (own_solution) {
|
||||
editorSelector = $('.own-editor')
|
||||
} else {
|
||||
editorSelector = $('.editor')
|
||||
}
|
||||
|
||||
editorSelector.each(function (index, element) {
|
||||
|
||||
// Resize frame on load
|
||||
this.resizeParentOfAceEditor(element);
|
||||
@ -279,12 +311,10 @@ var CodeOceanEditor = {
|
||||
session.setUseWrapMode(true);
|
||||
|
||||
// set regex for parsing error traces based on the mode of the main file.
|
||||
if ($(element).parent().data('role') == "main_file") {
|
||||
if ($(element).parent().data('role') === "main_file") {
|
||||
this.tracepositions_regex = this.regex_for_language.get($(element).data('mode'));
|
||||
}
|
||||
|
||||
var file_id = $(element).data('id');
|
||||
|
||||
/*
|
||||
* Register event handlers
|
||||
*/
|
||||
@ -326,9 +356,15 @@ var CodeOceanEditor = {
|
||||
});
|
||||
},
|
||||
|
||||
initializeFileTree: function () {
|
||||
$('#files').jstree($('#files').data('entries'));
|
||||
$('#files').on('click', 'li.jstree-leaf > a', function (event) {
|
||||
initializeFileTree: function (own_solution = false) {
|
||||
let filesInstance;
|
||||
if (own_solution) {
|
||||
filesInstance = $('#own-files');
|
||||
} else {
|
||||
filesInstance = $('#files');
|
||||
}
|
||||
filesInstance.jstree(filesInstance.data('entries'));
|
||||
filesInstance.on('click', 'li.jstree-leaf > a', function (event) {
|
||||
this.setActiveFile(
|
||||
$(event.target).parent().text(),
|
||||
parseInt($(event.target).parent().attr('id'))
|
||||
@ -793,7 +829,7 @@ var CodeOceanEditor = {
|
||||
const percentile75 = data['working_time_75_percentile'];
|
||||
const accumulatedWorkTimeUser = data['working_time_accumulated'];
|
||||
|
||||
const minTimeIntervention = 10 * 1000;
|
||||
const minTimeIntervention = 10 * 60 * 1000;
|
||||
|
||||
let timeUntilIntervention;
|
||||
if ((accumulatedWorkTimeUser - percentile75) > 0) {
|
||||
@ -879,6 +915,7 @@ var CodeOceanEditor = {
|
||||
|
||||
|
||||
initializeEverything: function () {
|
||||
CodeOceanEditor.editors = [];
|
||||
this.initializeRegexes();
|
||||
this.initializeCodePilot();
|
||||
$('.score, #development-environment').show();
|
||||
|
@ -9,8 +9,9 @@ CodeOceanEditorSubmissions = {
|
||||
* Submission-Creation
|
||||
*/
|
||||
createSubmission: function (initiator, filter, callback) {
|
||||
const editor = $('#editor');
|
||||
this.showSpinner(initiator);
|
||||
var url = $(initiator).data('url') || $('#editor').data('submissions-url');
|
||||
var url = $(initiator).data('url') || editor.data('submissions-url');
|
||||
|
||||
if (url === undefined) {
|
||||
const data = {
|
||||
@ -24,12 +25,12 @@ CodeOceanEditorSubmissions = {
|
||||
data: {
|
||||
submission: {
|
||||
cause: $(initiator).data('cause') || $(initiator).prop('id'),
|
||||
exercise_id: $('#editor').data('exercise-id'),
|
||||
exercise_id: editor.data('exercise-id') || $(initiator).data('exercise-id'),
|
||||
files_attributes: (filter || _.identity)(this.collectFiles())
|
||||
}
|
||||
},
|
||||
dataType: 'json',
|
||||
method: 'POST',
|
||||
method: $(initiator).data('http-method') || 'POST',
|
||||
url: url + '.json'
|
||||
});
|
||||
jqxhr.always(this.hideSpinner.bind(this));
|
||||
@ -191,20 +192,22 @@ CodeOceanEditorSubmissions = {
|
||||
}
|
||||
},
|
||||
|
||||
submitCode: function() {
|
||||
this.createSubmission($('#submit'), null, function (response) {
|
||||
submitCode: function(event) {
|
||||
const button = $(event.target) || $('#submit');
|
||||
this.createSubmission(button, null, function (response) {
|
||||
if (response.redirect) {
|
||||
this.editors = [];
|
||||
Turbolinks.clearCache();
|
||||
clearTimeout(this.autosaveTimer);
|
||||
Turbolinks.visit(response.redirect);
|
||||
} else if (response.status === 'container_depleted') {
|
||||
this.showContainerDepletedMessage();
|
||||
$('#submit').one('click', this.submitCode.bind(this));
|
||||
button.one('click', this.submitCode.bind(this));
|
||||
} else if (response.message) {
|
||||
$.flash.danger({
|
||||
text: response.message
|
||||
});
|
||||
$('#submit').one('click', this.submitCode.bind(this));
|
||||
button.one('click', this.submitCode.bind(this));
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -7,6 +7,14 @@ button i.fa-spin {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.own-editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.ace_scroller .ace_content {
|
||||
background: #FAFAFA;
|
||||
}
|
||||
}
|
||||
|
||||
/* this class is used for the edit view of an exercise. It needs the height set, as it does not automatically resize */
|
||||
.edit-frame {
|
||||
height: 400px;
|
||||
@ -26,6 +34,15 @@ button i.fa-spin {
|
||||
}
|
||||
}
|
||||
|
||||
.own-frame {
|
||||
display: none;
|
||||
min-height: 300px;
|
||||
|
||||
audio, img, video {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.score {
|
||||
display: none;
|
||||
vertical-align: bottom;
|
||||
@ -64,6 +81,10 @@ button i.fa-spin {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#own-files {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#outputInformation {
|
||||
#output {
|
||||
max-height: 500px;
|
||||
|
103
app/controllers/community_solutions_controller.rb
Normal file
103
app/controllers/community_solutions_controller.rb
Normal file
@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CommunitySolutionsController < ApplicationController
|
||||
include CommonBehavior
|
||||
include RedirectBehavior
|
||||
include SubmissionParameters
|
||||
|
||||
before_action :set_community_solution, only: %i[edit update]
|
||||
before_action :set_community_solution_lock, only: %i[edit]
|
||||
before_action :set_exercise_and_submission, only: %i[edit update]
|
||||
|
||||
# GET /community_solutions
|
||||
def index
|
||||
@community_solutions = CommunitySolution.all
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /community_solutions/1/edit
|
||||
def edit
|
||||
authorize!
|
||||
|
||||
# Be safe. Only allow access to this page if user has valid lock
|
||||
redirect_after_submit unless @community_solution_lock.present? && @community_solution_lock.active? && @community_solution_lock.user == current_user && @community_solution_lock.community_solution == @community_solution
|
||||
# We don't want to perform any of the following steps if we rendered already (e.g. due to a redirect)
|
||||
return if performed?
|
||||
|
||||
last_contribution = CommunitySolutionContribution.where(community_solution: @community_solution, timely_contribution: true, autosave: false, proposed_changes: true).order(created_at: :asc).last
|
||||
@files = []
|
||||
if last_contribution.blank?
|
||||
last_contribution = @community_solution.exercise
|
||||
new_readme_file = {content: '', file_type: FileType.find_by(file_extension: '.txt'), hidden: false, read_only: false, name: 'ReadMe', role: 'regular_file', context: @community_solution}
|
||||
@files << CodeOcean::File.create!(new_readme_file)
|
||||
end
|
||||
all_visible_files = last_contribution.files.select(&:visible)
|
||||
# Add the ReadMe file first
|
||||
@files += all_visible_files.select {|f| CodeOcean::File.find_by(id: f.file_id).context_type == 'CommunitySolution' }
|
||||
# Then, add all remaining files and sort them by name with extension
|
||||
@files += (all_visible_files - @files).sort_by(&:name_with_extension)
|
||||
|
||||
# Own Submission as a reference
|
||||
@own_files = @submission.collect_files.select(&:visible).sort_by(&:name_with_extension)
|
||||
# Remove the file_id from the second graph. Otherwise, the comparison and file-tree selection does not work as expected
|
||||
@own_files.map do |file|
|
||||
file.file_id = nil
|
||||
file.read_only = true
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /community_solutions/1
|
||||
def update
|
||||
authorize!
|
||||
contribution_params = submission_params
|
||||
cause = contribution_params.delete(:cause)
|
||||
contribution_params[:proposed_changes] = cause == 'change-community-solution'
|
||||
contribution_params[:autosave] = cause == 'autosave-community-solution'
|
||||
contribution_params.delete(:exercise_id)
|
||||
contribution_params[:community_solution] = @community_solution
|
||||
|
||||
# Acquire lock here! This is expensive but required for synchronization
|
||||
@community_solution_lock = ActiveRecord::Base.transaction do
|
||||
ActiveRecord::Base.connection.execute("LOCK #{CommunitySolutionLock.table_name} IN ACCESS EXCLUSIVE MODE")
|
||||
|
||||
lock = CommunitySolutionLock.where(user: current_user, community_solution: @community_solution).order(locked_until: :asc).last
|
||||
|
||||
if lock.active?
|
||||
contribution_params[:timely_contribution] = true
|
||||
# Update lock: Either expand the time (autosave) or return it (change / accept)
|
||||
new_lock_time = contribution_params[:autosave] ? 5.minutes.from_now : Time.zone.now
|
||||
lock.update!(locked_until: new_lock_time)
|
||||
else
|
||||
contribution_params[:timely_contribution] = false
|
||||
end
|
||||
# This is returned
|
||||
lock
|
||||
end
|
||||
|
||||
contribution_params[:community_solution_lock] = @community_solution_lock
|
||||
contribution_params[:working_time] = @community_solution_lock.working_time
|
||||
CommunitySolutionContribution.create(contribution_params)
|
||||
|
||||
redirect_after_submit
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize!
|
||||
authorize(@community_solution)
|
||||
end
|
||||
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_community_solution
|
||||
@community_solution = CommunitySolution.find(params[:id])
|
||||
end
|
||||
|
||||
def set_community_solution_lock
|
||||
@community_solution_lock = CommunitySolutionLock.find(params[:lock_id])
|
||||
end
|
||||
|
||||
def set_exercise_and_submission
|
||||
@exercise = @community_solution.exercise
|
||||
@submission = current_user.submissions.final.where(exercise_id: @community_solution.exercise.id).order('created_at DESC').first
|
||||
end
|
||||
end
|
@ -6,7 +6,7 @@ module FileParameters
|
||||
params.reject do |_, file_attributes|
|
||||
file = CodeOcean::File.find_by(id: file_attributes[:file_id])
|
||||
# avoid that public files from other contexts can be created
|
||||
file.nil? || file.hidden || file.read_only || (file.context_type == 'Exercise' && file.context_id != exercise.id)
|
||||
file.nil? || file.hidden || file.read_only || (file.context_type == 'Exercise' && file.context_id != exercise.id) || (file.context_type == 'CommunitySolution' && controller_name != 'community_solutions')
|
||||
end
|
||||
else
|
||||
[]
|
||||
|
@ -6,6 +6,11 @@ module RedirectBehavior
|
||||
def redirect_after_submit
|
||||
Rails.logger.debug { "Redirecting user with score:s #{@submission.normalized_score}" }
|
||||
if @submission.normalized_score.to_d == 1.0.to_d
|
||||
if redirect_to_community_solution?
|
||||
redirect_to_community_solution
|
||||
return
|
||||
end
|
||||
|
||||
# if user is external and has an own rfc, redirect to it and message him to clean up and accept the answer. (we need to check that the user is external,
|
||||
# otherwise an internal user could be shown a false rfc here, since current_user.id is polymorphic, but only makes sense for external users when used with rfcs.)
|
||||
# redirect 10 percent pseudorandomly to the feedback page
|
||||
@ -63,6 +68,40 @@ module RedirectBehavior
|
||||
|
||||
private
|
||||
|
||||
def redirect_to_community_solution
|
||||
url = edit_community_solution_path(@community_solution, lock_id: @community_solution_lock.id)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(url) }
|
||||
format.json { render(json: {redirect: url}) }
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to_community_solution?
|
||||
return false unless Java21Study.allow_redirect_to_community_solution?(current_user, @exercise)
|
||||
|
||||
@community_solution = CommunitySolution.find_by(exercise: @exercise)
|
||||
return false if @community_solution.blank?
|
||||
|
||||
last_contribution = CommunitySolutionContribution.where(community_solution: @community_solution).order(created_at: :asc).last
|
||||
|
||||
# Only redirect if last contribution is from another user.
|
||||
eligible = last_contribution.blank? || last_contribution.user != current_user
|
||||
return false unless eligible
|
||||
|
||||
# Acquire lock here! This is expensive but required for synchronization
|
||||
@community_solution_lock = ActiveRecord::Base.transaction do
|
||||
ActiveRecord::Base.connection.execute("LOCK #{CommunitySolutionLock.table_name} IN ACCESS EXCLUSIVE MODE")
|
||||
|
||||
# This is returned
|
||||
CommunitySolutionLock.find_or_create_by(community_solution: @community_solution, locked_until: Time.zone.now...) do |lock|
|
||||
lock.user = current_user
|
||||
lock.locked_until = 5.minutes.from_now
|
||||
end
|
||||
end
|
||||
|
||||
@community_solution_lock.user == current_user
|
||||
end
|
||||
|
||||
def redirect_to_user_feedback
|
||||
uef = UserExerciseFeedback.find_by(exercise: @exercise, user: current_user)
|
||||
url = if uef
|
||||
|
13
app/models/community_solution.rb
Normal file
13
app/models/community_solution.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CommunitySolution < ApplicationRecord
|
||||
belongs_to :exercise
|
||||
has_many :community_solution_locks
|
||||
has_many :community_solution_contributions
|
||||
has_and_belongs_to_many :users, polymorphic: true, through: :community_solution_contributions
|
||||
has_many :files, class_name: 'CodeOcean::File', through: :community_solution_contributions
|
||||
|
||||
def to_s
|
||||
"Gemeinschaftslösung für #{exercise}"
|
||||
end
|
||||
end
|
14
app/models/community_solution_contribution.rb
Normal file
14
app/models/community_solution_contribution.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CommunitySolutionContribution < ApplicationRecord
|
||||
include Creation
|
||||
include Context
|
||||
|
||||
belongs_to :community_solution
|
||||
belongs_to :community_solution_lock
|
||||
|
||||
validates :proposed_changes, boolean_presence: true
|
||||
validates :timely_contribution, boolean_presence: true
|
||||
validates :autosave, boolean_presence: true
|
||||
validates :working_time, presence: true
|
||||
end
|
18
app/models/community_solution_lock.rb
Normal file
18
app/models/community_solution_lock.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CommunitySolutionLock < ApplicationRecord
|
||||
include Creation
|
||||
|
||||
belongs_to :community_solution
|
||||
has_many :community_solution_contributions
|
||||
|
||||
validates :locked_until, presence: true
|
||||
|
||||
def active?
|
||||
Time.zone.now <= locked_until
|
||||
end
|
||||
|
||||
def working_time
|
||||
ActiveSupport::Duration.build(locked_until - created_at)
|
||||
end
|
||||
end
|
11
app/policies/community_solution_policy.rb
Normal file
11
app/policies/community_solution_policy.rb
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CommunitySolutionPolicy < AdminOnlyPolicy
|
||||
def edit?
|
||||
everyone
|
||||
end
|
||||
|
||||
def update?
|
||||
everyone
|
||||
end
|
||||
end
|
66
app/views/community_solutions/_form.html.slim
Normal file
66
app/views/community_solutions/_form.html.slim
Normal file
@ -0,0 +1,66 @@
|
||||
.exercise.clearfix
|
||||
div
|
||||
span.badge.badge-pill.badge-primary.float-right.score
|
||||
|
||||
h1 id="exercise-headline"
|
||||
i id="description-symbol" class=(@embed_options[:collapse_exercise_description] ? 'fa fa-chevron-right' : 'fa fa-chevron-down')
|
||||
=> @community_solution.model_name.human(count: 1)
|
||||
= @community_solution.exercise.title
|
||||
|
||||
#description-card.lead class=(@embed_options[:collapse_exercise_description] ? 'description-card-collapsed' : 'description-card')
|
||||
.card.border-success.mb-3
|
||||
.card-header
|
||||
i.fa.fa-info-circle.text-success
|
||||
strong.text-success
|
||||
=> t('community_solutions.help_us_out')
|
||||
= t('community_solutions.explanation')
|
||||
br
|
||||
i.fa.fa-flask.text-success
|
||||
strong.text-success
|
||||
=> t('community_solutions.research_status')
|
||||
== t('community_solutions.research_explanation')
|
||||
hr
|
||||
= render_markdown(@community_solution.exercise.description)
|
||||
|
||||
a#toggle href="#" data-show=t('shared.show') data-hide=t('shared.hide')
|
||||
- if @embed_options[:collapse_exercise_description]
|
||||
= t('shared.show')
|
||||
- else
|
||||
= t('shared.hide')
|
||||
.row.mt-4
|
||||
.col-xl-6
|
||||
h4
|
||||
= t('community_solutions.current_community_solution')
|
||||
#community-solution-editor.row
|
||||
.pr-0 class=(@community_solution.exercise.hide_file_tree ? 'd-none col-sm-3' : 'col-sm-3')
|
||||
.card.border-secondary
|
||||
.card-header.d-flex.justify-content-between.align-items-center.px-0.py-2
|
||||
.px-2 = I18n.t('exercises.editor_file_tree.file_root')
|
||||
.card-body.pt-0.pr-0.pl-1.pb-1
|
||||
#files data-entries=FileTree.new(@files).to_js_tree
|
||||
div class=(@community_solution.exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9')
|
||||
div.editor-col.col.p-0 id='frames'
|
||||
- @files.each do |file|
|
||||
= render('exercises/editor_frame', exercise: @community_solution.exercise, file: file)
|
||||
|
||||
.col-xl-6.container-fluid
|
||||
div.bg-dark.h-100.float-left.row style="width: 1px"
|
||||
div
|
||||
h4
|
||||
= t('community_solutions.your_submission')
|
||||
#own-solution-editor.row
|
||||
.pr-0 class=(@community_solution.exercise.hide_file_tree ? 'd-none col-sm-3' : 'col-sm-3')
|
||||
.card.border-secondary
|
||||
.card-header.d-flex.justify-content-between.align-items-center.px-0.py-2
|
||||
.px-2 = I18n.t('exercises.editor_file_tree.file_root')
|
||||
.card-body.pt-0.pr-0.pl-1.pb-1
|
||||
#own-files data-entries=FileTree.new(@own_files).to_js_tree
|
||||
div class=(@community_solution.exercise.hide_file_tree ? 'col-sm-12' : 'col-sm-9')
|
||||
div.editor-col.col.p-0 id='own-frames'
|
||||
- @own_files.each do |file|
|
||||
= render('exercises/editor_frame', exercise: @community_solution.exercise, file: file, own_solution: true)
|
||||
#statusbar.visible.mt-2 style="height: 5em"
|
||||
p.text-center
|
||||
= render('exercises/editor_button', classes: 'btn-lg btn-success ml-5 mr-3', data: {'data-url': community_solution_path(@community_solution), 'data-http-method': 'PUT', 'data-cause': 'change-community-solution', 'data-exercise-id': @community_solution.exercise.id}, icon: 'fa fa-send', id: 'submit', label: t('community_solutions.change_community_solution'))
|
||||
= render('exercises/editor_button', classes: 'btn-lg btn-secondary ml-5', data: {'data-url': community_solution_path(@community_solution), 'data-http-method': 'PUT', 'data-cause': 'accept-community-solution', 'data-exercise-id': @community_solution.exercise.id}, icon: 'fa fa-check', id: 'accept', label: t('community_solutions.accept_community_solution'))
|
||||
button style="display:none" id="autosave" data-url=community_solution_path(@community_solution) data-http-method='PUT' data-cause='autosave-community-solution' data-exercise-id=@community_solution.exercise.id
|
6
app/views/community_solutions/edit.html.slim
Normal file
6
app/views/community_solutions/edit.html.slim
Normal file
@ -0,0 +1,6 @@
|
||||
- content_for :head do
|
||||
// Force a full page reload, see https://github.com/turbolinks/turbolinks/issues/326.
|
||||
Otherwise, code might not be highlighted correctly (race condition)
|
||||
meta name='turbolinks-visit-control' content='reload'
|
||||
|
||||
== render 'form'
|
19
app/views/community_solutions/index.html.slim
Normal file
19
app/views/community_solutions/index.html.slim
Normal file
@ -0,0 +1,19 @@
|
||||
h1 Listing community_solutions
|
||||
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th
|
||||
th
|
||||
|
||||
tbody
|
||||
- @community_solutions.each do |community_solution|
|
||||
tr
|
||||
td = link_to 'Show', community_solution
|
||||
td = link_to 'Edit', edit_community_solution_path(community_solution)
|
||||
td = link_to 'Destroy', community_solution, data: { confirm: 'Are you sure?' }, method: :delete
|
||||
|
||||
br
|
||||
|
||||
= link_to 'New Community solution', new_community_solution_path
|
@ -1,4 +1,4 @@
|
||||
.frame data-executable=file.file_type.executable? data-filename=file.name_with_extension data-renderable=file.file_type.renderable? data-role=file.role data-binary=file.file_type.binary? data-context-type=file.context_type data-read-only=file.read_only
|
||||
div class=(defined?(own_solution) ? "own-frame" : "frame") data-executable=file.file_type.executable? data-filename=file.name_with_extension data-renderable=file.file_type.renderable? data-role=file.role data-binary=file.file_type.binary? data-context-type=file.context_type data-read-only=file.read_only
|
||||
- if file.file_type.binary?
|
||||
.binary-file data-file-id=file.ancestor_id
|
||||
- if file.file_type.renderable?
|
||||
@ -12,4 +12,4 @@
|
||||
= link_to(file.native_file.file.filename, file.native_file.url)
|
||||
- else
|
||||
.editor-content.d-none data-file-id=file.ancestor_id = file.content
|
||||
.editor data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-allow-auto-completion=exercise.allow_auto_completion.to_s data-id=file.id
|
||||
div class=(defined?(own_solution) ? "own-editor" : "editor") data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-allow-auto-completion=exercise.allow_auto_completion.to_s data-id=file.id
|
@ -55,7 +55,7 @@ html lang="#{I18n.locale || I18n.default_locale}"
|
||||
= render('flash')
|
||||
- if current_user.try(:admin?) or current_user.try(:teacher?) && !@embed_options[:hide_navbar]
|
||||
= yield(:breadcrumbs)
|
||||
- if (controller_name == "exercises" && action_name == "implement")
|
||||
- if (controller_name == "exercises" && action_name == "implement") || (controller_name == 'community_solutions' && action_name == 'edit')
|
||||
.container-fluid
|
||||
= yield
|
||||
- else
|
||||
|
Reference in New Issue
Block a user