Gustaf Neumann
XoWiki Content Flow - an XoWiki based workflow system implementing state-based behavior of wiki pages and forms
2021-09-15
WU Vienna
BSD-Style
2
-
-
+
+
Index: openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml,v
diff -u -N -r1.2.2.80 -r1.2.2.81
--- openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml 14 Aug 2022 08:10:43 -0000 1.2.2.80
+++ openacs-4/packages/xowf/catalog/xowf.de_DE.ISO-8859-1.xml 22 Aug 2022 16:08:55 -0000 1.2.2.81
@@ -61,6 +61,8 @@
Markierung dieser Frage l�schen
Frage markieren
Frage f�r eine sp�tere Bearbeitung markieren
+ Feedback-Dateien
+ Ziehen Sie Feedback-Dateien mit Drag & Drop hierher
Frische '%item_type%' Interaktion (%name%)
Generelles Feedback
Gehe zu dieser Version
Index: openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml,v
diff -u -N -r1.2.2.85 -r1.2.2.86
--- openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml 14 Aug 2022 08:10:44 -0000 1.2.2.85
+++ openacs-4/packages/xowf/catalog/xowf.en_US.ISO-8859-1.xml 22 Aug 2022 16:08:55 -0000 1.2.2.86
@@ -69,6 +69,8 @@
Remove flag for this question
Flag this Question
Flag this question for later
+ Feedback Files
+ Drag & Drop Feedback files here
Fresh '%item_type%' interaction (%name%)
General Feedback
Go to this revision
Index: openacs-4/packages/xowf/tcl/test-item-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/tcl/test-item-procs.tcl,v
diff -u -N -r1.7.2.240 -r1.7.2.241
--- openacs-4/packages/xowf/tcl/test-item-procs.tcl 14 Aug 2022 06:57:16 -0000 1.7.2.240
+++ openacs-4/packages/xowf/tcl/test-item-procs.tcl 22 Aug 2022 16:08:55 -0000 1.7.2.241
@@ -1374,7 +1374,91 @@
}
}
+ #----------------------------------------------------------------------
+ # Class: AssessmentInterface
+ # Method: render_feedback_files
+ #----------------------------------------------------------------------
+ :public method render_feedback_files {
+ {-question_name:required}
+ {-feedbackFiles {}}
+ } {
+ #
+ # Render feedback files which are children of the submit
+ # instances. Note that one submit instances contains the
+ # feedback files for all questions. For associating the files
+ # with the right quesitions, the content repository name has to
+ # start with "file:${questions_name}/*
+ #
+ # @param question_name name (prefix) for selecting files to be shown
+ # @param feedbackFiles is of pairs containing "item_id" and "name"
+ #
+ # @return HTML rendering
+ #
+ set chunkList [lmap pair $feedbackFiles {
+ lassign $pair item_id name
+ #ns_log warning "render_feedback_files: check '$name'"
+ if {![regexp {^file:(.*)/(.*)$} $name . qn fileName]} {
+ ns_log warning "render_feedback_files: ignoring file with unexpected name '$name'"
+ continue
+ }
+ if {$qn ne $question_name} {
+ #
+ # The found file is for a different question
+ #
+ #ns_log notice "render_feedback_files: required '$question_name' found '$qn'"
+ continue
+ }
+ set fileObj [::xowiki::File get_instance_from_db -item_id $item_id]
+ #
+ # Provide markup for delete likn. When the user has no rights
+ # to delete the file, do not offer the delete link.
+ #
+ set local_return_url [ad_return_url]
+ set package_id [$fileObj package_id]
+ set deleteLink [::$package_id make_link $fileObj delete local_return_url]
+ if {$deleteLink ne ""} {
+ set deleteLinkHTML \
+ [subst [ns_trim -delimiter | {
+ |
+ |
+ |
+ }]]
+ } else {
+ set deleteLinkHTML ""
+ }
+ set viewLink [::$package_id make_link $fileObj download]
+ if {$viewLink ne ""} {
+ set viewHref [subst {href="[ns_quotehtml $viewLink]"}]
+ set viewTitle {title="#xowiki.view#"}
+ } else {
+ set viewHref ""
+ set viewTitle ""
+ }
+ set iconName [::xowiki::CSS icon_name $fileName]
+ subst [ns_trim -delimiter | {
+ |
+ |
+ |
+ | [ns_quotehtml $fileName]$deleteLinkHTML
+ |
+ |
}]
+ }]
+
+ set HTML ""
+ if {[llength $chunkList] > 0} {
+ #
+ # Since the content will be post-processed via tDOM, we have
+ # to resolve the ADP tags already here.
+ #
+ append HTML \
+ {} \
+ [template::adp_parse_tags [join $chunkList \n]] \
+ {
}
+ }
+ return $HTML
+ }
+
#----------------------------------------------------------------------
# Class: AssessmentInterface
# Method: add_to_fc
@@ -2430,11 +2514,41 @@
::template::add_body_script -src urn:ad:js:jquery-ui
::template::add_body_script -script [subst -novariables {
+
+ function thumbnail_files_setup(element) {
+ // to be called on the elements of class ".thumbnail-file"
+ element.querySelectorAll('.thumbnail-file-text a.delete')
+ .forEach(el => el.addEventListener('click', event => {
+ // Get the "href" of the a.delete element
+ // and call this actions in the background
+ var href = event.currentTarget.getAttribute('href');
+ if (!href) {
+ console.log(".thumbnail-file does not have a proper delete link");
+ return
+ }
+ var fileIcon = event.currentTarget.parentElement.parentElement;
+ var request = new XMLHttpRequest();
+ request.open('GET', href, true);
+ request.onload = function() {
+ if (this.status >= 200 && this.status < 400) {
+ // Success!
+ fileIcon.parentNode.removeChild(fileIcon);
+ } else {
+ console.log('AJAX request returned bad return code: ' + this.status);
+ }
+ };
+ request.send();
+ event.preventDefault();
+ }));
+ };
+
$(document).ready(function(){
+ document.querySelectorAll('.thumbnail-file').forEach(el => thumbnail_files_setup(el));
+
$('.modal-dialog').draggable();
$('.modal .confirm').on('click', function(ev) {
//
- // Submit button of grading dialog was pressed.
+ // Popdown: "submit" button of grading dialog was pressed.
//
var id = ev.currentTarget.dataset.id;
var gradingBox = document.getElementById(id);
@@ -2484,6 +2598,19 @@
document.querySelector('#' + id + ' .feedback-label').classList.remove('hidden');
}
+ // Copy the content of the thumbnail files wrapper from the dialog
+ // to the main document and register the event handler.
+ let thumbnailFilesWrapper = document.querySelector('#' + id + ' .thumbnail-files-wrapper');
+ if (!thumbnailFilesWrapper) {
+ thumbnailFilesWrapper = document.createElement('div');
+ thumbnailFilesWrapper.className = 'thumbnail-files-wrapper';
+ document.querySelector('#' + id).appendChild(thumbnailFilesWrapper);
+ }
+ thumbnailFilesWrapper.innerHTML = document.querySelector('#thumbnail-files-wrapper').innerHTML;
+ //document.querySelector('#' + id + ' .thumbnail-files-wrapper').innerHTML =
+ // document.querySelector('#thumbnail-files-wrapper').innerHTML;
+ gradingBox.querySelectorAll('.thumbnail-file').forEach(el => thumbnail_files_setup(el));
+
var user_id = gradingBox.dataset.user_id;
var examGradingBox = document.getElementById('runtime-panel-' + user_id);
@@ -2522,7 +2649,7 @@
$('#grading-modal').on('shown.bs.modal', function (ev) {
//
- // Pop-up of grading dialog.
+ // Popup of grading dialog.
// Copy values from data attributes to input fields.
//
var gradingBox = ev.relatedTarget.parentElement;
@@ -2533,19 +2660,46 @@
pointsInput.value = gradingBox.dataset.achieved;
pointsInput.max = gradingBox.dataset.achievable;
document.getElementById('grading-comment').value = gradingBox.dataset.comment;
+ //document.getElementById('drop-zone').dataset.link = gradingBox.dataset.link;
+ var filesUpload = document.getElementById('js-upload-files');
+ filesUpload.dataset.file_name_prefix = gradingBox.dataset.question_name;
+ filesUpload.dataset.url = gradingBox.dataset.link;
+ filesUpload.dataset.disposition = "FileIconified";
+ //console.log("... URL " + filesUpload.dataset.url);
+
+ var feedBackFiles = gradingBox.getElementsByClassName("thumbnail-files-wrapper")\[0\];
+ //
+ // For legacy composite items, there is no "thumbnail-files-wrapper"
+ //
+ // console.log(feedBackFiles);
+ document.getElementById('thumbnail-files-wrapper').innerHTML =
+ (feedBackFiles ? feedBackFiles.innerHTML : "");
+
+ document.querySelectorAll('#grading-modal .thumbnail-file').forEach(el => thumbnail_files_setup(el));
+
// Tell confirm button to which grading box it belongs
var confirmButton = document.querySelector('#grading-modal-confirm');
confirmButton.dataset.id = gradingBox.id;
});
});
}]
+ set uploader_link [::[$examWf package_id] make_link $examWf file-upload]
+ set dropZone [::xowiki::BootstrapNavbarDropzone new \
+ -href $uploader_link \
+ -label #xowf.Feedback_files_dnd# \
+ -text "Text for SUBMIT label" \
+ -file_name_prefix "" \
+ -disposition File]
+ set dropZoneHTML [$dropZone asHTML]
+ #ns_log notice "dropZoneHTML=$dropZoneHTML"
+
return [::xowiki::bootstrap::modal_dialog \
-id grading-modal \
-title "#xowf.Grading#: " \
-subtitle "#xowf.question#: " \
- -body [ns_trim -delimiter | {
+ -body [subst [ns_trim -delimiter | {
|
- }] \
- ]
-
- return [ns_trim -delimiter | {
- |
- }]
-
+ |#xowf.Feedback_files#:
+ |
+ |
+ }]] \
+ ]
}
#----------------------------------------------------------------------
@@ -3035,12 +3146,20 @@
# userName and fullName.
#
foreach submission [$submissions children] {
+
+ set submission_item_id [$submission set item_id]
+ set feedbackFiles [xo::dc list_of_lists . {
+ select item_id, name from cr_items where parent_id = :submission_item_id
+ }]
+ #ns_log notice "item_id $submission_item_id : children <$feedbackFiles>"
+
$submission set online-exam-userName \
[acs_user::get_element \
-user_id [$submission creation_user] \
-element username]
$submission set online-exam-fullName \
[::xo::get_user_name [$submission creation_user]]
+ $submission set online-exam-feedbackFiles $feedbackFiles
}
return $submissions
@@ -3210,6 +3329,10 @@
:uplevel [list $node appendXML $XML]
}
}
+ :method "dom node appendXML" {domNode xquery XML} {
+ set node [$domNode selectNodes $xquery]
+ :uplevel [list $node appendXML $XML]
+ }
:method "dom node delete" {domNode xquery} {
set nodes [$domNode selectNodes $xquery]
foreach node $nodes {
@@ -3247,6 +3370,7 @@
{-submission:object,required}
{-runtime_panel_view:required}
{-exam_state:required}
+ {-feedbackFiles ""}
} {
#
# Post-process the HTML of a question by adding information of
@@ -3340,6 +3464,10 @@
}
}
+ #
+ # Composite grading-boxes are done, now general code over all
+ # grading-boxes.
+ #
set submission_state [$submission state]
#set noManualGrading [expr {$submission_state ne "done" || $exam_state eq "published"}]
set noManualGrading [expr {$exam_state eq "published"}]
@@ -3351,9 +3479,16 @@
set item_type [expr {[$item_node hasAttribute "data-item_type"]
? [$item_node getAttribute "data-item_type"]
: ""}]
+
+ set feedbackFilesHTML [:render_feedback_files \
+ -question_name $qn \
+ -feedbackFiles $feedbackFiles]
+
+ #ns_log notice "FEEDBACK '$qn' feedbackFiles $feedbackFiles HTML\n$feedbackFilesHTML"
#ns_log notice "... QN '$qn' item_type '$item_type'" \
"submission state $submission_state" \
"exam state $exam_state noManualGrading $noManualGrading"
+
if {$noManualGrading} {
:dom class add $grading_box {a[contains(@class,'manual-grade')]} \
[::xowiki::CSS class d-none]
@@ -3389,7 +3524,7 @@
set warning [::template::icon \
-class [xowiki::CSS class text-warning] \
-name warn ]
- set pencil [::template::icon -name pencil]
+ set pencil [::template::icon -name pencil]
:dom node replaceXML $grading_box \
{span[@class='points']} \
[dict get $warning HTML]
@@ -3405,7 +3540,7 @@
:dom node replaceXML $grading_box \
{a[@class='manual-grade']/span/..} \
[dict get $pencil HTML]
-
+
} else {
:dom node replace $grading_box {span[@class='points']} {::html::t $achieved}
if {$achievable ne ""} {
@@ -3414,6 +3549,35 @@
}
}
#
+ # handling of legacy items
+ #
+ set changes [expr {[::xowiki::CSS toolkit] eq "bootstrap"
+ ? {bs-toggle toggle bs-target target}
+ : {toggle bs-toggle target bs-target}}]
+ foreach node [$grading_box selectNodes {a[@class='manual-grade']}] {
+ foreach {old new} $changes {
+ if {[$node hasAttribute data-$old]} {
+ $node setAttribute data-$new [$node getAttribute data-$old]
+ $node removeAttribute data-$old
+ }
+ }
+ }
+
+ if {$feedbackFilesHTML ne ""} {
+ #ns_log notice "REPLACE thumbnail-files-wrapper in\n[$grading_box asXML]"
+ if {[llength [$grading_box selectNodes {div[@class='thumbnail-files-wrapper']}]] == 0} {
+ #
+ # Must be a legacy composite item without the thumbnail
+ # wrapper.
+ #
+ $grading_box appendXML \
+ {}
+ }
+ :dom node replaceXML $grading_box \
+ {div[@class='thumbnail-files-wrapper']} \
+ $feedbackFilesHTML
+ }
+ #
# When "comment" is empty, do not show the label.
#
:dom node replace $grading_box {span[@class='comment']} {::html::t $comment}
@@ -3431,6 +3595,7 @@
$grading_box setAttribute data-achieved $achieved
$grading_box setAttribute data-achievable $achievable
$grading_box setAttribute data-comment $comment
+ $grading_box setAttribute data-link [::[$submission package_id] make_link $submission file-upload]
#
# Feedback handling (should be merged with the individual feedback)
@@ -3596,7 +3761,8 @@
-manual_grading [:dict_value $manual_gradings $user_id] \
-submission $submission \
-exam_state [$examWf state] \
- -runtime_panel_view $runtime_panel_view]
+ -runtime_panel_view $runtime_panel_view \
+ -feedbackFiles [$submission set online-exam-feedbackFiles]]
if {$with_signature} {
set sha256 [ns_md string -digest sha256 $answeredAnswerAttributes]
@@ -3663,7 +3829,7 @@
- [expr {$with_exam_heading ? "
$heading
" : ""}]
+ [expr {$with_exam_heading ? "$heading
" : ""}]
$runtime_panel
$signatureString
@@ -6060,6 +6226,7 @@
| $data_attribute-target='#grading-modal'>
| [::xowiki::bootstrap::icon -name pencil]
|
+ |
|
}]]
}
@@ -7299,7 +7466,31 @@
# }
# }
+namespace eval ::xowf {
+ ::xowf::WorkflowPage ad_instproc render_thumbnails {upload_info} {
+ Renderer of the thumbnail file(s)
+
+ @param upload_info dict containing the "file_object" and "file_name"
+ @return HTML content
+ } {
+ dict with upload_info {
+ set parent_id ${:item_id}
+ set feedbackFiles [xo::dc list_of_lists . {
+ select item_id, name from cr_items where parent_id = :parent_id
+ }]
+ if {[regexp {^([^/]+)/} $file_name . qn]} {
+ set HTML [:QM render_feedback_files \
+ -question_name $qn \
+ -feedbackFiles $feedbackFiles]
+ } else {
+ set HTML "$file_name created"
+ }
+ }
+ return $HTML
+ }
+}
+
namespace eval ::xowf::test_item {
#
# Copy the default policy (policy1) from xowiki and add elements for
Index: openacs-4/packages/xowf/www/resources/test-item.css
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/xowf/www/resources/test-item.css,v
diff -u -N -r1.1.2.44 -r1.1.2.45
--- openacs-4/packages/xowf/www/resources/test-item.css 14 Aug 2022 07:32:01 -0000 1.1.2.44
+++ openacs-4/packages/xowf/www/resources/test-item.css 22 Aug 2022 16:08:55 -0000 1.1.2.45
@@ -20,6 +20,17 @@
color: black;
padding: 2px 10px 4px 10px;
}
+div.xowiki-content div.single_exam div.runtime-panel h3 {
+ font-weight: normal;
+ font-size: 20px;
+}
+div.single_exam div.grading-box .thumbnail-files-wrapper {
+ margin-top: 4px;
+}
+div.xowiki-content div.single_exam h4 {
+ font-size: 16px;
+ margin-bottom: 4px;
+}
div.single_exam div.runtime-panel .data {
color: #888;
}
@@ -101,19 +112,28 @@
/* keep display for div.upload_interaction div.form-label */
+/* Definition for Bootstrap 3 */
div.mc_interaction div.form-label,
div.sc_interaction div.form-label,
div.text_interaction div.form-label
{
display: none;
}
+/* Definition for Bootstrap 5 */
+div.mc_interaction label.form-label,
+div.sc_interaction label.form-label,
+div.text_interaction label.form-label
+{
+ display: none;
+}
div.mc_interaction,
div.sc_interaction,
div.short_text_interaction,
div.composite_interaction,
div.text_interaction,
div.reorder_interaction {
+ margin: 0 3em 0 3em;
padding: 15px 0px 15px 0px;
}
div.mc_interaction label,
@@ -393,3 +413,31 @@
form.Form-edit-grading-scheme input::-webkit-outer-spin-button,
form.Form-edit-grading-scheme input::-webkit-inner-spin-button { margin-left: 2px; }
*/
+
+#grading-modal ul.dropZone {
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+}
+.thumbnail-file {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid #CAD0D2;
+ border-radius: 4px;
+ background-color: #FCFCFC;
+ padding: 2px;
+}
+.thumbnail-file-icon {
+ font-size: larger;
+}
+.thumbnail-file-text {
+ gap: 3px;
+ font-family: sans-serif;
+ font-size: 0.8em;
+}
+.thumbnail-files {
+ display: flex;
+ gap: 10px;
+}