MDL-68493 core_contentbank: implement list view
authorBas Brands <bas@moodle.com>
Wed, 20 May 2020 09:42:55 +0000 (11:42 +0200)
committerBas Brands <bas@moodle.com>
Wed, 20 May 2020 14:27:58 +0000 (16:27 +0200)
18 files changed:
contentbank/amd/build/search.min.js
contentbank/amd/build/search.min.js.map
contentbank/amd/build/selectors.min.js
contentbank/amd/build/selectors.min.js.map
contentbank/amd/build/sort.min.js [new file with mode: 0644]
contentbank/amd/build/sort.min.js.map [new file with mode: 0644]
contentbank/amd/src/search.js
contentbank/amd/src/selectors.js
contentbank/amd/src/sort.js [new file with mode: 0644]
contentbank/classes/content.php
contentbank/classes/output/bankcontent.php
contentbank/templates/bankcontent.mustache
contentbank/templates/bankcontent/toolbar.mustache
contentbank/tests/behat/sort_content.feature [new file with mode: 0644]
lang/en/contentbank.php
theme/boost/scss/moodle/contentbank.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css

index 9090c9b..16c80b0 100644 (file)
Binary files a/contentbank/amd/build/search.min.js and b/contentbank/amd/build/search.min.js differ
index a3ddca1..0e06258 100644 (file)
Binary files a/contentbank/amd/build/search.min.js.map and b/contentbank/amd/build/search.min.js.map differ
index c7322b9..35b476a 100644 (file)
Binary files a/contentbank/amd/build/selectors.min.js and b/contentbank/amd/build/selectors.min.js differ
index 99b3b56..36d959f 100644 (file)
Binary files a/contentbank/amd/build/selectors.min.js.map and b/contentbank/amd/build/selectors.min.js.map differ
diff --git a/contentbank/amd/build/sort.min.js b/contentbank/amd/build/sort.min.js
new file mode 100644 (file)
index 0000000..b2785d1
Binary files /dev/null and b/contentbank/amd/build/sort.min.js differ
diff --git a/contentbank/amd/build/sort.min.js.map b/contentbank/amd/build/sort.min.js.map
new file mode 100644 (file)
index 0000000..db47d34
Binary files /dev/null and b/contentbank/amd/build/sort.min.js.map differ
index bd5cb55..e604abc 100644 (file)
@@ -36,7 +36,7 @@ import {debounce} from 'core/utils';
 export const init = () => {
     const pendingPromise = new Pending();
 
-    const root = $(selectors.elements.main);
+    const root = $(selectors.regions.contentbank);
     registerListenerEvents(root);
 
     pendingPromise.resolve();
@@ -120,10 +120,10 @@ const toggleSearchResultsView = async(body, searchQuery) => {
  * @return {Array}
  */
 const filterContents = (body, searchTerm) => {
-    const contents = Array.from(body.find(selectors.elements.cbfile));
+    const contents = Array.from(body.find(selectors.elements.listitem));
     const searchResults = [];
     contents.forEach((content) => {
-        const contentName = content.getAttribute('data-file');
+        const contentName = content.getAttribute('data-name');
         if (searchTerm === '' || contentName.toLowerCase().includes(searchTerm.toLowerCase())) {
             // The content matches the search criteria so it should be displayed and hightlighted.
             searchResults.push(content);
index 080f85f..b8ca6a6 100644 (file)
@@ -37,18 +37,26 @@ const getDataSelector = (name, value) => {
 export default {
     regions: {
         cbcontentname: getDataSelector('region', 'cb-content-name'),
+        contentbank: getDataSelector('region', 'contentbank'),
+        filearea: getDataSelector('region', 'filearea')
     },
     actions: {
         search: getDataSelector('action', 'searchcontent'),
         clearSearch: getDataSelector('action', 'clearsearchcontent'),
+        viewgrid: getDataSelector('action', 'viewgrid'),
+        viewlist: getDataSelector('action', 'viewlist'),
+        sortname: getDataSelector('action', 'sortname'),
+        sortdate: getDataSelector('action', 'sortdate'),
+        sortsize: getDataSelector('action', 'sortsize'),
+        sorttype: getDataSelector('action', 'sorttype')
     },
     elements: {
-        cbfile: '.cb-file',
+        listitem: '.cb-listitem',
         cbnavbarbreadcrumb: '.cb-navbar-breadbrumb',
         cbnavbartotalsearch: '.cb-navbar-totalsearch',
         clearsearch: '.input-group-append .clear-icon',
-        main: '#region-main',
         searchicon: '.input-group-append .search-icon',
         searchinput: '#searchinput',
+        sortbutton: '.cb-btnsort'
     },
 };
diff --git a/contentbank/amd/src/sort.js b/contentbank/amd/src/sort.js
new file mode 100644 (file)
index 0000000..d76bd2f
--- /dev/null
@@ -0,0 +1,193 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Content bank UI actions.
+ *
+ * @module     core_contentbank/sort
+ * @package    core_contentbank
+ * @copyright  2020 Bas Brands <bas@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import selectors from 'core_contentbank/selectors';
+import {get_string as getString} from 'core/str';
+import Prefetch from 'core/prefetch';
+
+
+/**
+ * Set up the contentbank views.
+ *
+ * @method init
+ */
+export const init = () => {
+    const contentBank = document.querySelector(selectors.regions.contentbank);
+    Prefetch.prefetchStrings('contentbank', ['sortbyx', 'sortbyxreverse', 'contentname',
+        'lastmodified', 'size', 'type']);
+    registerListenerEvents(contentBank);
+};
+
+
+/**
+ * Register contentbank related event listeners.
+ *
+ * @method registerListenerEvents
+ * @param {HTMLElement} contentBank The DOM node of the content bank
+ */
+const registerListenerEvents = (contentBank) => {
+
+    // The search.
+    const fileArea = document.querySelector(selectors.regions.filearea);
+    const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);
+
+    // The view buttons.
+    const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);
+    const viewList = contentBank.querySelector(selectors.actions.viewlist);
+
+    viewGrid.addEventListener('click', () => {
+        contentBank.classList.remove('view-list');
+        contentBank.classList.add('view-grid');
+        viewGrid.classList.add('active');
+        viewList.classList.remove('active');
+    });
+
+    viewList.addEventListener('click', () => {
+        contentBank.classList.remove('view-grid');
+        contentBank.classList.add('view-list');
+        viewList.classList.add('active');
+        viewGrid.classList.remove('active');
+    });
+
+    // Sort by file name alphabetical
+    const sortByName = contentBank.querySelector(selectors.actions.sortname);
+    sortByName.addEventListener('click', () => {
+        const ascending = updateSortButtons(contentBank, sortByName);
+        updateSortOrder(fileArea, shownItems, 'data-file', ascending);
+    });
+
+    // Sort by date.
+    const sortByDate = contentBank.querySelector(selectors.actions.sortdate);
+    sortByDate.addEventListener('click', () => {
+        const ascending = updateSortButtons(contentBank, sortByDate);
+        updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);
+    });
+
+    // Sort by size.
+    const sortBySize = contentBank.querySelector(selectors.actions.sortsize);
+    sortBySize.addEventListener('click', () => {
+        const ascending = updateSortButtons(contentBank, sortBySize);
+        updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);
+    });
+
+    // Sort by type
+    const sortByType = contentBank.querySelector(selectors.actions.sorttype);
+    sortByType.addEventListener('click', () => {
+        const ascending = updateSortButtons(contentBank, sortByType);
+        updateSortOrder(fileArea, shownItems, 'data-type', ascending);
+    });
+};
+
+/**
+ * Update the sort button view.
+ *
+ * @method updateSortButtons
+ * @param {HTMLElement} contentBank The DOM node of the contentbank button
+ * @param {HTMLElement} sortButton The DOM node of the sort button
+ * @return {Bool} sort ascending
+ */
+const updateSortButtons = (contentBank, sortButton) => {
+    const sortButtons = contentBank.querySelectorAll(selectors.elements.sortbutton);
+
+    sortButtons.forEach((button) => {
+        if (button !== sortButton) {
+            button.classList.remove('dir-asc');
+            button.classList.remove('dir-desc');
+            button.classList.add('dir-none');
+
+            updateButtonTitle(button, false);
+        }
+    });
+
+    let ascending = true;
+
+    if (sortButton.classList.contains('dir-none')) {
+        sortButton.classList.remove('dir-none');
+        sortButton.classList.add('dir-asc');
+    } else if (sortButton.classList.contains('dir-asc')) {
+        sortButton.classList.remove('dir-asc');
+        sortButton.classList.add('dir-desc');
+        ascending = false;
+    } else if (sortButton.classList.contains('dir-desc')) {
+        sortButton.classList.remove('dir-desc');
+        sortButton.classList.add('dir-asc');
+    }
+
+    updateButtonTitle(sortButton, ascending);
+
+    return ascending;
+};
+
+/**
+ * Update the button title.
+ *
+ * @method updateButtonTitle
+ * @param {HTMLElement} button Button to update
+ * @param {Bool} ascending Sort direction
+ * @return {Promise} string promise
+ */
+const updateButtonTitle = (button, ascending) => {
+
+    const sortString = (ascending ? 'sortbyxreverse' : 'sortbyx');
+
+    return getString(button.dataset.string, 'contentbank')
+    .then(columnName => {
+        return getString(sortString, 'core', columnName);
+    })
+    .then(sortByString => {
+        button.setAttribute('title', sortByString);
+        return sortByString;
+    })
+    .catch();
+};
+
+/**
+ * Update the sort order of the itemlist and update the DOM
+ *
+ * @method updateSortOrder
+ * @param {HTMLElement} fileArea the Dom container for the itemlist
+ * @param {Array} itemList Nodelist of Dom elements
+ * @param {String} attribute, the attribut to sort on
+ * @param {Bool} ascending, Sort Ascending
+ */
+const updateSortOrder = (fileArea, itemList, attribute, ascending) => {
+    const sortList = [].slice.call(itemList).sort(function(a, b) {
+
+        let aa = a.getAttribute(attribute);
+        let bb = b.getAttribute(attribute);
+        if (!isNaN(aa)) {
+           aa = parseInt(aa);
+           bb = parseInt(bb);
+        }
+
+        if (ascending) {
+            return aa > bb ? 1 : -1;
+        } else {
+            return aa < bb ? 1 : -1;
+        }
+    });
+    sortList.forEach(function (listItem) {
+        fileArea.appendChild(listItem);
+    });
+};
\ No newline at end of file
index 5e8c7bc..769395f 100644 (file)
@@ -85,6 +85,16 @@ abstract class content {
         return $this->content->contenttype;
     }
 
+
+    /**
+     * Returns $this->content->timemodified.
+     *
+     * @return int  $this->content->timemodified.
+     */
+    public function get_timemodified(): int {
+        return $this->content->timemodified;
+    }
+
     /**
      * Updates content_bank table with information in $this->content.
      *
index 2ea5c4d..6574b04 100644 (file)
@@ -75,18 +75,26 @@ class bankcontent implements renderable, templatable {
         global $PAGE;
 
         $PAGE->requires->js_call_amd('core_contentbank/search', 'init');
+        $PAGE->requires->js_call_amd('core_contentbank/sort', 'init');
 
         $data = new stdClass();
         $contentdata = array();
         foreach ($this->contents as $content) {
-            $record = $content->get_content();
+            $file = $content->get_file();
+            $filesize = $file ? $file->get_filesize() : 0;
+            $mimetype = $file ? get_mimetype_description($file) : '';
             $contenttypeclass = $content->get_content_type().'\\contenttype';
             $contenttype = new $contenttypeclass($this->context);
             $name = $content->get_name();
             $contentdata[] = array(
                 'name' => $name,
+                'title' => strtolower($name),
                 'link' => $contenttype->get_view_url($content),
-                'icon' => $contenttype->get_icon($content)
+                'icon' => $contenttype->get_icon($content),
+                'timemodified' => $content->get_timemodified(),
+                'bytes' => $filesize,
+                'size' => display_size($filesize),
+                'type' => $mimetype
             );
         }
         $data->contents = $contentdata;
index 8c43626..0826a65 100644 (file)
     {
         "contents": [
             {
-                "name": "accordion.h5p",
+                "name": "Accordion.h5p",
+                "title": "accordion.h5p",
+                "timemodified": 1589792272,
+                "size": "699.3KB",
+                "bytes": 716126,
+                "type": "Archive (H5P)",
                 "link": "http://something/contentbank/contenttype/h5p/view.php?url=http://something/pluginfile.php/1/contentbank/public/accordion.h5p",
                 "icon" : "http://something/theme/image.php/boost/core/1581597850/f/h5p-64"
             },
     }
 
 }}
-<div class="d-flex justify-content-between flex-column flex-sm-row">
-    <div class="cb-search-container mb-2">
-        {{>core_contentbank/bankcontent/search}}
-    </div>
-    <div class="cb-toolbar-container mb-2">
-        {{>core_contentbank/bankcontent/toolbar}}
+<div class="content-bank-container view-grid" data-region="contentbank">
+    <div class="d-flex justify-content-between flex-column flex-sm-row">
+        <div class="cb-search-container mb-2">
+            {{>core_contentbank/bankcontent/search}}
+        </div>
+        <div class="cb-toolbar-container mb-2 d-flex">
+            {{>core_contentbank/bankcontent/toolbar}}
+        </div>
     </div>
-</div>
-<div class="content-bank-container pb-3 border">
-    <div class="content-bank">
-        <div class="cb-navbar bg-light p-2 border-bottom">
-            <div class="cb-navbar-breadbrumb">
-                {{#pix}} i/folder {{/pix}}
-            </div>
-            <div class="cb-navbar-totalsearch d-none">
+    <div class="pb-3 border">
+        <div class="content-bank">
+            <div class="cb-navbar bg-light p-2 border-bottom">
+                <div class="cb-navbar-breadbrumb">
+                    {{#pix}} i/folder {{/pix}}
+                </div>
+                <div class="cb-navbar-totalsearch d-none">
+                </div>
             </div>
-        </div>
-        <div class="cb-content-wrapper d-flex flex-wrap p-2">
-        {{#contents}}
-            <div class="cb-file position-relative mb-2" data-file="{{{name}}}">
-                <div class="p-2">
-                    <div class="cb-thumbnail mb-1 text-center">
-                        <img class="icon iconsize-big" alt="{{{name}}}" title="{{{name}}}" src="{{{ icon }}}">
+            <div class="cb-content-wrapper d-flex px-2" data-region="filearea">
+                <div class="cb-heading bg-white">
+                    <div class="cb-file cb-column d-flex">
+                        <div class="title">{{#str}} contentname, contentbank {{/str}}</div>
+                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="contentname" data-action="sortname"
+                            title="{{#str}} sortbyx, core, {{#str}} contentname, contentbank {{/str}} {{/str}}">
+                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                        </button>
                     </div>
-
-                    {{#link}}
-                        <a href="{{{ link }}}" class="stretched-link" title="{{{name}}}">
-                    {{/link}}
-                            <span class="cb-name word-break-all clamp-2 text-center" data-region="cb-content-name">
+                    <div class="cb-date cb-column d-flex">
+                        <div class="title">{{#str}} lastmodified, contentbank {{/str}}</div>
+                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="lastmodified" data-action="sortdate"
+                        title="{{#str}} sortbyx, core, {{#str}} lastmodified, contentbank {{/str}} {{/str}}">
+                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                        </button>
+                    </div>
+                    <div class="cb-size cb-column d-flex">
+                        <div class="title">{{#str}} size, contentbank {{/str}}</div>
+                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="size" data-action="sortsize"
+                        title="{{#str}} sortbyx, core, {{#str}} size, contentbank {{/str}} {{/str}}">
+                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                        </button>
+                    </div>
+                    <div class="cb-type cb-column d-flex last">
+                        <div class="title">{{#str}} type, contentbank {{/str}}</div>
+                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="type" data-action="sorttype"
+                        title="{{#str}} sortbyx, core, {{#str}} size, contentbank {{/str}} {{/str}}">
+                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                        </button>
+                    </div>
+                </div>
+            {{#contents}}
+                <div class="cb-listitem"
+                    data-file="{{{ title }}}"
+                    data-name="{{{ name }}}"
+                    data-bytes="{{ bytes }}"
+                    data-timemodified="{{ timemodified }}"
+                    data-type="{{{ type }}}">
+                    <div class="cb-file cb-column position-relative">
+                        <div class="cb-thumbnail" role="img" aria-label="{{{ name }}}"
+                        style="background-image: url('{{{ icon }}}');">
+                        </div>
+                        <a href="{{{ link }}}" class="cb-link stretched-link">
+                            <span class="cb-name word-break-all clamp-2" data-region="cb-content-name">
                                 {{{ name }}}
                             </span>
-                    {{#link}}
                         </a>
-                    {{/link}}
+                    </div>
+                    <div class="cb-date cb-column small">
+                        {{#userdate}} {{ timemodified }}, {{#str}} strftimedatetimeshort, core_langconfig {{/str}} {{/userdate}}
+                    </div>
+                    <div class="cb-size cb-column small">
+                        {{ size }}
+                    </div>
+                    <div class="cb-type cb-column last small">
+                        {{{ type }}}
+                    </div>
                 </div>
+            {{/contents}}
             </div>
-        {{/contents}}
         </div>
     </div>
 </div>
index 88f4a4c..04c762a 100644 (file)
     }
 
 }}
-<div class="content-bank-toolbar card border-0 mb-3">
-    <div class="content-bank">
-        <div class="cb-toolbar float-sm-right">
-        {{#tools}}
-            {{#link}}<a href="{{{ link }}}" title="{{{ name }}}">{{/link}}
-                <div class="cb-tool icon-no-margin btn btn-secondary btn-lg">
-                    {{#pix}} {{{ icon }}} {{/pix}} <span class="sr-only">{{{ name }}}</span>
-                </div>
-            {{#link}}</a>{{/link}}
-        {{/tools}}
-        </div>
-    </div>
-</div>
+
+{{#tools}}
+    <a href="{{{ link }}}" class="icon-no-margin btn btn-secondary" title="{{{ name }}}">
+        {{#pix}} {{{ icon }}} {{/pix}} {{{ name }}}
+    </a>
+{{/tools}}
+<button class="icon-no-margin btn btn-secondary active ml-2"
+title="{{#str}}  displayicons, contentbank  {{/str}}"
+data-action="viewgrid">
+    {{#pix}}a/view_icon_active, core, {{#str}} displayicons, contentbank {{/str}} {{/pix}}
+</button>
+<button class="icon-no-margin btn btn-secondary"
+title="{{#str}} displaydetails, contentbank {{/str}}"
+data-action="viewlist">
+    {{#pix}}t/viewdetails, core, {{#str}} displaydetails, contentbank {{/str}} {{/pix}}
+</button>
\ No newline at end of file
diff --git a/contentbank/tests/behat/sort_content.feature b/contentbank/tests/behat/sort_content.feature
new file mode 100644 (file)
index 0000000..b4ca7ad
--- /dev/null
@@ -0,0 +1,32 @@
+@core @core_contentbank @contentbank_h5p @javascript
+Feature: Sort content in the content bank
+  In order to temporarily organise the content of the content bank
+  As an admin
+  I need to be able to sort the content bank in various ways
+
+  Background:
+    Given the following "contentbank content" exist:
+        | contextlevel | reference | contenttype       | user  | contentname          |
+        | System       |           | contenttype_h5p   | admin | Dragon_santjordi.h5p |
+        | System       |           | contenttype_h5p   | admin | mathsbook.h5p        |
+        | System       |           | contenttype_h5p   | admin | historybook.h5p      |
+        | System       |           | contenttype_h5p   | admin | santjordi.h5p        |
+        | System       |           | contenttype_h5p   | admin | santjordi_rose.h5p   |
+        | System       |           | contenttype_h5p   | admin | SantJordi_book       |
+
+  Scenario: Admins can order content in the content bank
+    Given I log in as "admin"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add the "Navigation" block if not present
+    And I expand "Site pages" node
+    And I click on "Content bank" "link"
+    When I click on "Display contentbank with file details" "button"
+    And I click on "Sort by Content name ascending" "button"
+    And "Dragon_santjordi.h5p" "text" should appear before "historybook.h5p" "text"
+    And "historybook.h5p" "text" should appear before "mathsbook.h5p" "text"
+    And "SantJordi_book" "text" should appear before "santjordi_rose.h5p" "text"
+    And I click on "Sort by Content name descending" "button"
+    And "historybook.h5p" "text" should appear before "Dragon_santjordi.h5p" "text"
+    And "mathsbook.h5p" "text" should appear before "historybook.h5p" "text"
+    Then "santjordi_rose.h5p" "text" should appear before "SantJordi_book" "text"
index 14b1c98..55108d3 100644 (file)
@@ -38,9 +38,12 @@ $string['eventcontentviewed'] = 'Content viewed';
 $string['errordeletingcontentfromcategory'] = 'Error deleting content from category {$a}.';
 $string['deletecontent'] = 'Delete content';
 $string['deletecontentconfirm'] = 'Are you sure you want to delete the content <em>\'{$a->name}\'</em> and all associated files? This action cannot be undone.';
+$string['displaydetails'] = 'Display contentbank with file details';
+$string['displayicons'] = 'Display contentbank with icons';
 $string['file'] = 'Upload content';
 $string['file_help'] = 'Files may be stored in the content bank for use in courses. Only files used by content types enabled on the site may be uploaded.';
 $string['itemsfound'] = '{$a} items found';
+$string['lastmodified'] = 'Last modified';
 $string['name'] = 'Content';
 $string['nopermissiontodelete'] = 'You do not have permission to delete content.';
 $string['nopermissiontomanage'] = 'You do not have permission to manage content.';
@@ -55,6 +58,8 @@ $string['privacy:metadata:userid'] = 'The ID of the user creating or modifying c
 $string['rename'] = 'Rename';
 $string['renamecontent'] = 'Rename content';
 $string['searchcontentbankbyname'] = 'Search for content by name';
+$string['size'] = 'Size';
 $string['timecreated'] = 'Time created';
+$string['type'] = 'Type';
 $string['unsupported'] = 'This content type is not supported.';
 $string['upload'] = 'Upload';
index 4b075cb..49e47bc 100644 (file)
-@include media-breakpoint-down(sm) {
-    .content-bank-container .cb-file {
-        flex-basis: 50%;
+.content-bank-container {
+    .cb-content-wrapper {
+        padding: 0.5rem;
+        min-height: 140px;
+        max-height: 500px;
+        overflow-x: auto;
+        flex-wrap: wrap;
+    }
+    .cb-thumbnail {
+        width: 24px;
+        height: 24px;
+        background-repeat: no-repeat;
+        background-position: center;
+        background-size: cover;
     }
-}
+    &.view-grid {
+        .cb-listitem {
+            margin-bottom: 0.5rem;
+        }
+
+        @include media-breakpoint-down(sm) {
+            .cb-listitem {
+                flex-basis: 50%;
+            }
+        }
+
+        @include media-breakpoint-up(sm) {
+            .cb-listitem {
+                max-width: 120px;
+                min-width: 120px;
+            }
+        }
 
-@include media-breakpoint-up(sm) {
-    .content-bank-container .cb-file {
-        max-width: 120px;
-        min-width: 120px;
+        .cb-name {
+            text-align: center;
+        }
+        .cb-file {
+            padding: 0.5rem;
+        }
+        .cb-thumbnail {
+            width: 64px;
+            height: 64px;
+            margin-left: auto;
+            margin-right: auto;
+            margin-bottom: 0.5rem;
+        }
+        .cb-heading,
+        .cb-date,
+        .cb-size,
+        .cb-type {
+            display: none;
+        }
     }
-}
 
-.content-bank-container {
-    min-height: 140px;
+    &.view-list {
+        .cb-content-wrapper {
+            padding: 0 0.5rem;
+            flex-direction: column;
+            flex-wrap: nowrap;
+        }
+
+        .cb-thumbnail {
+            margin-right: 0.5rem;
+        }
+
+        .cb-listitem,
+        .cb-heading {
+            display: flex;
+            flex-wrap: wrap;
+            width: 100%;
+            border-bottom: $border-width solid $border-color;
+        }
+
+        .cb-column {
+            display: flex;
+            padding: 0.25rem;
+        }
+
+        .cb-column {
+            border-right: $border-width solid $border-color;
+        }
+
+        @include media-breakpoint-down(sm) {
+            .cb-column {
+                flex: 0 0 50%;
+                max-width: 50%;
+            }
+        }
+
+        @include media-breakpoint-up(sm) {
+            .cb-heading {
+                position: sticky;
+                top: 0;
+                z-index: 1;
+            }
+
+            .cb-file,
+            .cb-date {
+                flex: 0 0 35%;
+                max-width: 35%;
+            }
+            .cb-size,
+            .cb-type {
+                flex: 0 0 15%;
+                max-width: 15%;
+            }
+            .cb-column.last {
+                border-right: 0;
+            }
+        }
+
+        .cb-btnsort {
+            span {
+                display: none;
+            }
+            &.dir-none .default,
+            &.dir-asc .asc,
+            &.dir-desc .desc {
+                display: block;
+            }
+        }
+    }
 }
\ No newline at end of file
index a71a92b..add3537 100644 (file)
@@ -12489,17 +12489,101 @@ table.calendartable caption {
 .cal_courses_flt {
   color: #868e96; }
 
+.content-bank-container .cb-content-wrapper {
+  padding: 0.5rem;
+  min-height: 140px;
+  max-height: 500px;
+  overflow-x: auto;
+  flex-wrap: wrap; }
+
+.content-bank-container .cb-thumbnail {
+  width: 24px;
+  height: 24px;
+  background-repeat: no-repeat;
+  background-position: center;
+  background-size: cover; }
+
+.content-bank-container.view-grid .cb-listitem {
+  margin-bottom: 0.5rem; }
+
 @media (max-width: 767.98px) {
-  .content-bank-container .cb-file {
+  .content-bank-container.view-grid .cb-listitem {
     flex-basis: 50%; } }
 
 @media (min-width: 576px) {
-  .content-bank-container .cb-file {
+  .content-bank-container.view-grid .cb-listitem {
     max-width: 120px;
     min-width: 120px; } }
 
-.content-bank-container {
-  min-height: 140px; }
+.content-bank-container.view-grid .cb-name {
+  text-align: center; }
+
+.content-bank-container.view-grid .cb-file {
+  padding: 0.5rem; }
+
+.content-bank-container.view-grid .cb-thumbnail {
+  width: 64px;
+  height: 64px;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 0.5rem; }
+
+.content-bank-container.view-grid .cb-heading,
+.content-bank-container.view-grid .cb-date,
+.content-bank-container.view-grid .cb-size,
+.content-bank-container.view-grid .cb-type {
+  display: none; }
+
+.content-bank-container.view-list .cb-content-wrapper {
+  padding: 0 0.5rem;
+  flex-direction: column;
+  flex-wrap: nowrap; }
+
+.content-bank-container.view-list .cb-thumbnail {
+  margin-right: 0.5rem; }
+
+.content-bank-container.view-list .cb-listitem,
+.content-bank-container.view-list .cb-heading {
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+  border-bottom: 1px solid #dee2e6; }
+
+.content-bank-container.view-list .cb-column {
+  display: flex;
+  padding: 0.25rem; }
+
+.content-bank-container.view-list .cb-column {
+  border-right: 1px solid #dee2e6; }
+
+@media (max-width: 767.98px) {
+  .content-bank-container.view-list .cb-column {
+    flex: 0 0 50%;
+    max-width: 50%; } }
+
+@media (min-width: 576px) {
+  .content-bank-container.view-list .cb-heading {
+    position: sticky;
+    top: 0;
+    z-index: 1; }
+  .content-bank-container.view-list .cb-file,
+  .content-bank-container.view-list .cb-date {
+    flex: 0 0 35%;
+    max-width: 35%; }
+  .content-bank-container.view-list .cb-size,
+  .content-bank-container.view-list .cb-type {
+    flex: 0 0 15%;
+    max-width: 15%; }
+  .content-bank-container.view-list .cb-column.last {
+    border-right: 0; } }
+
+.content-bank-container.view-list .cb-btnsort span {
+  display: none; }
+
+.content-bank-container.view-list .cb-btnsort.dir-none .default,
+.content-bank-container.view-list .cb-btnsort.dir-asc .asc,
+.content-bank-container.view-list .cb-btnsort.dir-desc .desc {
+  display: block; }
 
 /* course.less */
 /* COURSE CONTENT */
index d5ea721..1b2c012 100644 (file)
@@ -12702,17 +12702,101 @@ table.calendartable caption {
 .cal_courses_flt {
   color: #868e96; }
 
+.content-bank-container .cb-content-wrapper {
+  padding: 0.5rem;
+  min-height: 140px;
+  max-height: 500px;
+  overflow-x: auto;
+  flex-wrap: wrap; }
+
+.content-bank-container .cb-thumbnail {
+  width: 24px;
+  height: 24px;
+  background-repeat: no-repeat;
+  background-position: center;
+  background-size: cover; }
+
+.content-bank-container.view-grid .cb-listitem {
+  margin-bottom: 0.5rem; }
+
 @media (max-width: 767.98px) {
-  .content-bank-container .cb-file {
+  .content-bank-container.view-grid .cb-listitem {
     flex-basis: 50%; } }
 
 @media (min-width: 576px) {
-  .content-bank-container .cb-file {
+  .content-bank-container.view-grid .cb-listitem {
     max-width: 120px;
     min-width: 120px; } }
 
-.content-bank-container {
-  min-height: 140px; }
+.content-bank-container.view-grid .cb-name {
+  text-align: center; }
+
+.content-bank-container.view-grid .cb-file {
+  padding: 0.5rem; }
+
+.content-bank-container.view-grid .cb-thumbnail {
+  width: 64px;
+  height: 64px;
+  margin-left: auto;
+  margin-right: auto;
+  margin-bottom: 0.5rem; }
+
+.content-bank-container.view-grid .cb-heading,
+.content-bank-container.view-grid .cb-date,
+.content-bank-container.view-grid .cb-size,
+.content-bank-container.view-grid .cb-type {
+  display: none; }
+
+.content-bank-container.view-list .cb-content-wrapper {
+  padding: 0 0.5rem;
+  flex-direction: column;
+  flex-wrap: nowrap; }
+
+.content-bank-container.view-list .cb-thumbnail {
+  margin-right: 0.5rem; }
+
+.content-bank-container.view-list .cb-listitem,
+.content-bank-container.view-list .cb-heading {
+  display: flex;
+  flex-wrap: wrap;
+  width: 100%;
+  border-bottom: 1px solid #dee2e6; }
+
+.content-bank-container.view-list .cb-column {
+  display: flex;
+  padding: 0.25rem; }
+
+.content-bank-container.view-list .cb-column {
+  border-right: 1px solid #dee2e6; }
+
+@media (max-width: 767.98px) {
+  .content-bank-container.view-list .cb-column {
+    flex: 0 0 50%;
+    max-width: 50%; } }
+
+@media (min-width: 576px) {
+  .content-bank-container.view-list .cb-heading {
+    position: sticky;
+    top: 0;
+    z-index: 1; }
+  .content-bank-container.view-list .cb-file,
+  .content-bank-container.view-list .cb-date {
+    flex: 0 0 35%;
+    max-width: 35%; }
+  .content-bank-container.view-list .cb-size,
+  .content-bank-container.view-list .cb-type {
+    flex: 0 0 15%;
+    max-width: 15%; }
+  .content-bank-container.view-list .cb-column.last {
+    border-right: 0; } }
+
+.content-bank-container.view-list .cb-btnsort span {
+  display: none; }
+
+.content-bank-container.view-list .cb-btnsort.dir-none .default,
+.content-bank-container.view-list .cb-btnsort.dir-asc .asc,
+.content-bank-container.view-list .cb-btnsort.dir-desc .desc {
+  display: block; }
 
 /* course.less */
 /* COURSE CONTENT */