From b121e998a941d38525551571bd648b36402bf571 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Thu, 29 Jun 2023 11:40:39 +0200 Subject: [PATCH 1/2] MDL-78283 theme_boost: parent focus css helper --- theme/boost/scss/moodle/core.scss | 17 +++++++++++++++++ theme/boost/style/moodle.css | 14 ++++++++++++++ theme/classic/style/moodle.css | 14 ++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index dd3cf2a9afd..ae76d6da4d4 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -2643,6 +2643,23 @@ input[disabled] { } } +/* + * Helpers to show elements only when a parent element has focus or hover. + */ + +// Add this class to the element to hide. +.v-parent-focus { + opacity: 0; + visibility: hidden; +} + +// Add this class to the parent element to control focus. +.focus-control:focus-within .v-parent-focus, +.focus-control:hover .v-parent-focus { + opacity: 1; + visibility: visible; +} + // Emoji picker. $picker-width: 350px !default; $picker-width-xs: 320px !default; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 9cc7cadda0f..5f6da4f482e 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -25448,6 +25448,20 @@ input[disabled] { width: 300px; } } +/* + * Helpers to show elements only when a parent element has focus or hover. + */ +.v-parent-focus { + opacity: 0; + visibility: hidden; +} + +.focus-control:focus-within .v-parent-focus, +.focus-control:hover .v-parent-focus { + opacity: 1; + visibility: visible; +} + .emoji-picker { width: 350px; height: 400px; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 188ecac4098..7cdb91e485c 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -25448,6 +25448,20 @@ input[disabled] { width: 300px; } } +/* + * Helpers to show elements only when a parent element has focus or hover. + */ +.v-parent-focus { + opacity: 0; + visibility: hidden; +} + +.focus-control:focus-within .v-parent-focus, +.focus-control:hover .v-parent-focus { + opacity: 1; + visibility: visible; +} + .emoji-picker { width: 350px; height: 400px; From 6e1fff1a573e30c90285c37a4e3fe950276535cb Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Wed, 28 Jun 2023 16:28:39 +0200 Subject: [PATCH 2/2] MDL-78283 core_courseformat: group mode dropdown --- course/format/classes/base.php | 15 ++ .../classes/output/local/content/cm.php | 33 +-- .../output/local/content/cm/groupmode.php | 209 ++++++++++++++++++ .../templates/local/content/cm.mustache | 2 +- .../local/content/cm/activity.mustache | 6 +- ...pmode_info.mustache => groupmode.mustache} | 27 ++- .../tests/behat/activity_groupmode.feature | 65 ++++++ lang/en/group.php | 2 + .../output/icon_system_fontawesome.php | 1 - pix/i/groupn.png | Bin 345 -> 630 bytes pix/i/groupn.svg | 6 +- 11 files changed, 326 insertions(+), 40 deletions(-) create mode 100644 course/format/classes/output/local/content/cm/groupmode.php rename course/format/templates/local/content/cm/{groupmode_info.mustache => groupmode.mustache} (59%) create mode 100644 course/format/tests/behat/activity_groupmode.feature diff --git a/course/format/classes/base.php b/course/format/classes/base.php index faa2757dd95..196bcc71280 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -1431,6 +1431,21 @@ abstract class base { return $PAGE->user_is_editing() && has_all_capabilities($capabilities, $coursecontext); } + /** + * Check if the group mode can be displayed. + * @param cm_info $cm the activity module + * @return bool + */ + public function show_groupmode(cm_info $cm): bool { + if (!plugin_supports('mod', $cm->modname, FEATURE_GROUPS, false)) { + return false; + } + if (!has_capability('moodle/course:manageactivities', $cm->context)) { + return false; + } + return true; + } + /** * Allows to specify for modinfo that section is not available even when it is visible and conditionally available. * diff --git a/course/format/classes/output/local/content/cm.php b/course/format/classes/output/local/content/cm.php index 2dff8d8a455..4afaee7c6f2 100644 --- a/course/format/classes/output/local/content/cm.php +++ b/course/format/classes/output/local/content/cm.php @@ -69,6 +69,9 @@ class cm implements named_templatable, renderable { /** @var string the activity availability class name */ protected $availabilityclass; + /** @var string the activity groupmode badge class name */ + protected $groupmodeclass; + /** * Constructor. * @@ -90,6 +93,7 @@ class cm implements named_templatable, renderable { $this->cmnameclass = $format->get_output_classname('content\\cm\\cmname'); $this->controlmenuclass = $format->get_output_classname('content\\cm\\controlmenu'); $this->availabilityclass = $format->get_output_classname('content\\cm\\availability'); + $this->groupmodeclass = $format->get_output_classname('content\\cm\\groupmode'); } /** @@ -314,32 +318,9 @@ class cm implements named_templatable, renderable { * @return bool the module has group mode information */ protected function add_groupmode_data(stdClass &$data, renderer_base $output): bool { - if (!plugin_supports('mod', $this->mod->modname, FEATURE_GROUPS, false)) { - return false; - } - - if (!has_capability('moodle/course:manageactivities', $this->mod->context)) { - return false; - } - - switch ($this->mod->effectivegroupmode) { - case SEPARATEGROUPS: - $groupicon = 'i/groups'; - $groupalt = get_string('groupsseparate', 'group'); - break; - case VISIBLEGROUPS: - $groupicon = 'i/groupv'; - $groupalt = get_string('groupsvisible', 'group'); - break; - default: - return false; - } - - $data->groupmodeinfo = (object) [ - 'groupicon' => $groupicon, - 'groupalt' => $groupalt, - ]; - return true; + $groupmode = new $this->groupmodeclass($this->format, $this->section, $this->mod); + $data->groupmodeinfo = $groupmode->export_for_template($output); + return !empty($data->groupmodeinfo); } /** diff --git a/course/format/classes/output/local/content/cm/groupmode.php b/course/format/classes/output/local/content/cm/groupmode.php new file mode 100644 index 00000000000..e487e074d5a --- /dev/null +++ b/course/format/classes/output/local/content/cm/groupmode.php @@ -0,0 +1,209 @@ +. + +namespace core_courseformat\output\local\content\cm; + +use cm_info; +use core_courseformat\base as course_format; +use core_courseformat\output\local\courseformat_named_templatable; +use core\output\named_templatable; +use core\output\choicelist; +use core\output\local\dropdown\status; +use pix_icon; +use renderable; +use section_info; +use stdClass; + +/** + * Base class to render an activity group mode badge. + * + * @package core_courseformat + * @copyright 2023 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class groupmode implements named_templatable, renderable { + + use courseformat_named_templatable; + + /** @var course_format the course format */ + protected $format; + + /** @var section_info the section object */ + private $section; + + /** @var cm_info the course module instance */ + protected $mod; + + /** + * Constructor. + * + * @param course_format $format the course format + * @param section_info $section the section info + * @param cm_info $mod the course module ionfo + */ + public function __construct( + course_format $format, + section_info $section, + cm_info $mod, + ) { + $this->format = $format; + $this->section = $section; + $this->mod = $mod; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param \renderer_base $output typically, the renderer that's calling this function + * @return stdClass|null data context for a mustache template + */ + public function export_for_template(\renderer_base $output): ?stdClass { + if (!$this->format->show_groupmode($this->mod)) { + return null; + } + if ($this->format->show_editor()) { + return $this->build_editor_data($output); + } + // If the group mode is not editable, the no groups badge is not displayed. + if ($this->mod->effectivegroupmode === NOGROUPS) { + return null; + } + return $this->build_static_data($output); + } + + /** + * Build the data for the static badge. + * @param \renderer_base $output + * @return stdClass + */ + protected function build_static_data(\renderer_base $output): stdClass { + switch ($this->mod->effectivegroupmode) { + case SEPARATEGROUPS: + $groupalt = get_string('groupsseparate', 'group'); + $groupicon = $this->get_action_icon('cmSeparateGroups', $groupalt); + break; + case VISIBLEGROUPS: + $groupalt = get_string('groupsvisible', 'group'); + $groupicon = $this->get_action_icon('cmVisibleGroups', $groupalt); + break; + case NOGROUPS: + default: + $groupalt = get_string('groupsnone', 'group'); + $groupicon = $this->get_action_icon('cmNoGroups', $groupalt); + break; + } + $data = (object) [ + 'groupicon' => $output->render($groupicon), + 'groupalt' => $groupalt, + 'isInteractive' => false, + ]; + return $data; + } + + /** + * Build the data for the interactive dropdown. + * @param \renderer_base $output + * @return stdClass + */ + protected function build_editor_data(\renderer_base $output): stdClass { + $choice = $this->get_choice_list(); + $result = $this->get_dropdown_data($output, $choice); + $result->autohide = ($this->mod->effectivegroupmode === NOGROUPS); + return $result; + } + + /** + * Build the data for the interactive dropdown. + * @param \renderer_base $output + * @param choicelist $choice the choice list + * @return stdClass + */ + protected function get_dropdown_data(\renderer_base $output, choicelist $choice): stdClass { + $buttondata = $this->build_static_data($output); + $dropdown = new status( + $buttondata->groupicon, + $choice, + ['dialogwidth' => status::WIDTH['big']], + ); + $dropdown->set_dialog_width(status::WIDTH['small']); + $dropdown->set_position(status::POSITION['end']); + return (object) [ + 'isInteractive' => true, + 'groupicon' => $buttondata->groupicon, + 'groupalt' => $buttondata->groupalt, + 'dropwdown' => $dropdown->export_for_template($output), + ]; + } + + /** + * Create a choice list for the dropdown. + * @return choicelist the choice list + */ + protected function get_choice_list(): choicelist { + $choice = new choicelist(); + $choice->add_option( + NOGROUPS, + get_string('groupsnone', 'group'), + $this->get_option_data(null, 'cmNoGroups', $this->mod->id) + ); + $choice->add_option( + SEPARATEGROUPS, + get_string('groupsseparate', 'group'), + $this->get_option_data('groupsseparate', 'cmSeparateGroups', $this->mod->id) + ); + $choice->add_option( + VISIBLEGROUPS, + get_string('groupsvisible', 'group'), + $this->get_option_data('groupsvisible', 'cmVisibleGroups', $this->mod->id) + ); + $choice->set_selected_value($this->mod->effectivegroupmode); + return $choice; + } + + /** + * Get the data for the option. + * @param string|null $name the name of the option + * @param string $action the state action of the option + * @param int $id the id of the module + * @return array + */ + private function get_option_data(?string $name, string $action, int $id): array { + return [ + 'description' => ($name) ? get_string("groupmode_{$name}_help", 'group') : null, + // The dropdown icons are decorative, so we don't need to provide alt text. + 'icon' => $this->get_action_icon($action), + 'extras' => [ + 'data-id' => $id, + 'data-action' => $action, + ] + ]; + } + + /** + * Get the group mode icon. + * @param string $groupmode the group mode + * @param string $groupalt the alt text + * @return pix_icon + */ + protected function get_action_icon(string $groupmode, string $groupalt = ''): pix_icon { + $icons = [ + 'cmNoGroups' => 'i/groupn', + 'cmSeparateGroups' => 'i/groups', + 'cmVisibleGroups' => 'i/groupv', + ]; + return new pix_icon($icons[$groupmode], $groupalt); + } +} diff --git a/course/format/templates/local/content/cm.mustache b/course/format/templates/local/content/cm.mustache index dd9009db296..a336a2786ab 100644 --- a/course/format/templates/local/content/cm.mustache +++ b/course/format/templates/local/content/cm.mustache @@ -62,7 +62,7 @@ {{> core_course/activitychooserbuttonactivity}} {{/editing}} -
{{$ core_courseformat/local/content/cm/bulkselect }} diff --git a/course/format/templates/local/content/cm/activity.mustache b/course/format/templates/local/content/cm/activity.mustache index 532d9e0ef28..dd08f606fd2 100644 --- a/course/format/templates/local/content/cm/activity.mustache +++ b/course/format/templates/local/content/cm/activity.mustache @@ -105,9 +105,9 @@ {{! Group mode }} {{#groupmodeinfo}}
- {{$ core_courseformat/local/content/cm/groupmode_info}} - {{> core_courseformat/local/content/cm/groupmode_info}} - {{/ core_courseformat/local/content/cm/groupmode_info}} + {{$ core_courseformat/local/content/cm/groupmode}} + {{> core_courseformat/local/content/cm/groupmode}} + {{/ core_courseformat/local/content/cm/groupmode}}
{{/groupmodeinfo}} diff --git a/course/format/templates/local/content/cm/groupmode_info.mustache b/course/format/templates/local/content/cm/groupmode.mustache similarity index 59% rename from course/format/templates/local/content/cm/groupmode_info.mustache rename to course/format/templates/local/content/cm/groupmode.mustache index 342741f30c8..737f1c37f9a 100644 --- a/course/format/templates/local/content/cm/groupmode_info.mustache +++ b/course/format/templates/local/content/cm/groupmode.mustache @@ -26,14 +26,29 @@ "groupalt": "Visible groups" } }} -
{{groupalt}} + {{/ buttoncontent }} + {{/ core/local/dropdown/status}} + {{/dropwdown}} +{{/isInteractive}} +{{^isInteractive}} +
- {{#groupicon}} - {{#pix}}{{groupicon}}, core, {{groupalt}}{{/pix}} - {{/groupicon}} + {{#groupicon}}{{{groupicon}}}{{/groupicon}} {{#groupalt}}
{{groupalt}}
{{/groupalt}}
+{{/isInteractive}} diff --git a/course/format/tests/behat/activity_groupmode.feature b/course/format/tests/behat/activity_groupmode.feature new file mode 100644 index 00000000000..29fb9dbc4a7 --- /dev/null +++ b/course/format/tests/behat/activity_groupmode.feature @@ -0,0 +1,65 @@ +@core @core_courseformat +Feature: Verify activity group mode interface. + In order to edit the course activity group mode + As a teacher + I need to be able edit the group mode form the page course + + Background: + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | category | 0 | + | numsections | 3 | + And the following "groups" exist: + | name | course | idnumber | + | G1 | C1 | GI1 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | groupmode | + | forum | Activity sample 1 | Test forum description | C1 | sample1 | 1 | 0 | + | forum | Activity sample 2 | Test forum description | C1 | sample2 | 1 | 1 | + | forum | Activity sample 3 | Test forum description | C1 | sample3 | 1 | 2 | + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + Given I am on the "C1" "Course" page logged in as "teacher1" + And I turn editing mode on + + @javascript + Scenario: Teacher can see the group mode badges in both edit and no edit mode + Given "Visible groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity" + And "Separate groups" "icon" should exist in the "Activity sample 2" "activity" + And "Visible groups" "icon" should exist in the "Activity sample 3" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 3" "activity" + When I turn editing mode off + Then "Visible groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity" + And "Separate groups" "icon" should exist in the "Activity sample 2" "activity" + And "Visible groups" "icon" should exist in the "Activity sample 3" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 3" "activity" + + @javascript + Scenario: Teacher can edit the group mode using the activity group mode badge + Given I click on "Separate groups" "icon" in the "Activity sample 2" "activity" + And I click on "Visible groups" "link" in the "Activity sample 2" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 2" "activity" + And "Visible groups" "icon" should exist in the "Activity sample 2" "activity" + When I click on "Visible groups" "icon" in the "Activity sample 2" "activity" + And I click on "Separate groups" "link" in the "Activity sample 2" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity" + And "Separate groups" "icon" should exist in the "Activity sample 2" "activity" + Then I click on "Separate groups" "icon" in the "Activity sample 2" "activity" + And I click on "No groups" "link" in the "Activity sample 2" "activity" + And "Separate groups" "icon" should not exist in the "Activity sample 2" "activity" + And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity" + And I open "Activity sample 2" actions menu + When I click on "No groups" "icon" in the "Activity sample 2" "activity" + And I click on "Separate groups" "link" in the "Activity sample 2" "activity" + And "Separate groups" "icon" should exist in the "Activity sample 2" "activity" diff --git a/lang/en/group.php b/lang/en/group.php index dba929c3e5b..fa7c71ee6fd 100644 --- a/lang/en/group.php +++ b/lang/en/group.php @@ -115,6 +115,8 @@ $string['groupmembers'] = 'Group members'; $string['groupmemberssee'] = 'See group members'; $string['groupmembersselected'] = 'Members of selected group'; $string['groupmode'] = 'Group mode'; +$string['groupmode_groupsseparate_help'] = 'Each group member can only see their own group, others are invisible'; +$string['groupmode_groupsvisible_help'] = 'Each group member works in their own group, but can also see other groups'; $string['groupmode_help'] = 'This setting has 3 options: * No groups diff --git a/lib/classes/output/icon_system_fontawesome.php b/lib/classes/output/icon_system_fontawesome.php index 8b491a3da0f..56a727cfa9c 100644 --- a/lib/classes/output/icon_system_fontawesome.php +++ b/lib/classes/output/icon_system_fontawesome.php @@ -253,7 +253,6 @@ class icon_system_fontawesome extends icon_system_font { 'core:i/grading' => 'fa-magic', 'core:i/gradingnotifications' => 'fa-bell-o', 'core:i/groupevent' => 'fa-group', - 'core:i/groupn' => 'fa-user', 'core:i/group' => 'fa-users', 'core:i/home' => 'fa-home', 'core:i/hide' => 'fa-eye', diff --git a/pix/i/groupn.png b/pix/i/groupn.png index c8e1a7eee1318b6a3e818ffb54c0b3513cacf3e3..42319c4ebe83aec7d5a320a3744cd2bbfc13b805 100644 GIT binary patch literal 630 zcmV-+0*U>JP)~^nxJxRDC$M>fm@Zx1rn1u|6>hK%=I44m zppt>{EqfYd&O*XK0|noVW=^|zySMv&w*W7AYGAxrES8QDv7v?D#u5HxlgD3&E@@4v zzRC(vST1dl#sGjqbN*<*yP29mes%-i;HSiB9}jxvwBYhnse%H2+cqL9tAlO7NTresCv#+3^tWzzr`La(5lu zM}(W|pc}vxOeC-r;c(!Unqsr0Yt4?Jcw9o>%RjKPZ6-BsA7oF<#6K%eJX6s0y#S z{Ky9t1W`X4>{cI?z`kXQTk(u4n;B*CGPcIH7m1 z1YBAL?uKI&R9IT^tr25H(K5EF3C#F~d%(OGuB4&*{YaWz%@*6%;RVkIS9g8sdJ`J4 Q4*&oF07*qoM6N<$f+SoR0{{R3 literal 345 zcmV-f0jB -]> \ No newline at end of file + + +