diff --git a/example.json b/example.json index e09aecf..dbc2d95 100644 --- a/example.json +++ b/example.json @@ -4,6 +4,20 @@ "_debugFile": "" } +// course.json - _globals._extensions +"_devtools": { + "_navButton": { + "ariaLabel": "Developer tools", + "_navOrder": 9000, + "_showLabel": true, + "navLabel": "Dev Tools" + }, + "_navTooltip": { + "_isEnabled": true, + "text": "Developer tools" + } +} + /* _debugFile example (e.g. dev.json) diff --git a/js/adapt-devtools.js b/js/adapt-devtools.js index 85e52d8..7b3599e 100644 --- a/js/adapt-devtools.js +++ b/js/adapt-devtools.js @@ -5,6 +5,10 @@ import wait from 'core/js/wait'; import logging from 'core/js/logging'; import location from 'core/js/location'; import AdaptModel from 'core/js/models/adaptModel'; +import NavigationButtonModel from 'core/js/models/NavigationButtonModel'; +import NavigationButtonView from 'core/js/views/NavigationButtonView'; +import navigation from 'core/js/navigation'; +import tooltips from 'core/js/tooltips'; import DevtoolsModel from './devtools-model'; import PassHalfFail from './pass-half-fail'; import ToggleBanking from './toggle-banking'; @@ -377,44 +381,37 @@ class DevtoolsView extends Backbone.View { } } -class DevtoolsNavigationView extends Backbone.View { +class DevtoolsNavigationButtonView extends NavigationButtonView { - initialize () { - this.render = this.render.bind(this); - const template = Handlebars.templates.devtoolsNavigation; - this.$el = $(template()); - $('html').addClass('devtools-enabled').toggleClass('devtools-extended', Adapt.devtools.get('_extended')); - if (this.$el.is('a') || this.$el.is('button')) this.$el.on('click', this.onDevtoolsClicked.bind(this)); - else this.$el.find('a, button').on('click', this.onDevtoolsClicked.bind(this)); - this.listenTo(Adapt, 'pageView:postRender menuView:postRender', this.onContentRendered); - // ensure render occurs at least once (_isReady will not change to true on menus that exclude content objects) - this.listenToOnce(Adapt, 'pageView:postRender menuView:postRender', this.render); - } - - render () { - $('.nav__inner').append(this.$el); - return this; + events () { + return { + click: 'onDevtoolsClicked' + }; } - remove () { - this.$el.remove(); - this.stopListening(); - return this; + static get template() { + return 'devtoolsNavigationButton.jsx'; } - deferredRender () { - _.defer(this.render); + attributes () { + return { + ...super.attributes(), + 'data-tooltip-id': this.model.get('_id'), + 'aria-haspopup': 'dialog' + }; } - onContentRendered (view) { - if (view.model.get('_id') === location._currentId) { - this.stopListening(view.model, 'change:_isReady', this.deferredRender); - this.listenToOnce(view.model, 'change:_isReady', this.deferredRender); - } + initialize (options) { + super.initialize(options); + this.listenTo(Adapt, { remove: this.remove }); + tooltips.register({ + _id: this.model.get('_id'), + ...this.model.get('_navTooltip') || {} + }); } onDevtoolsClicked (event) { - if (event && event.preventDefault) event.preventDefault(); + event.preventDefault(); drawer.openCustomView(new DevtoolsView().$el, false); } @@ -426,13 +423,37 @@ Adapt.once('courseModel:dataLoaded', () => { function initNavigationView() { if (!Adapt.devtools.get('_isEnabled')) return; - if (navigationView) navigationView.remove(); - navigationView = new DevtoolsNavigationView(); + if (navigationView) navigation.removeButton(navigationView); + $('html').addClass('devtools-enabled').toggleClass('devtools-extended', Adapt.devtools.get('_extended')); + const devtoolsGlobals = Adapt.course.get('_globals')?._extensions?._devtools ?? {}; + const { + ariaLabel = 'Developer tools', + _navOrder = 9000, + _showLabel = true, + navLabel = 'Dev Tools' + } = devtoolsGlobals._navButton ?? {}; + const { _navTooltip = {} } = devtoolsGlobals; + const model = new NavigationButtonModel({ + _id: 'devtools', + _order: _navOrder, + _showLabel, + _classes: 'nav__devtools-btn devtools__nav-btn', + _iconClasses: 'icon-cog', + _role: 'button', + ariaLabel: ariaLabel || navLabel, + text: navLabel, + _navTooltip + }); + navigationView = new DevtoolsNavigationButtonView({ model }); + navigation.addButton(navigationView); } Adapt.once('adapt:initialize devtools:enable', () => { + Adapt.on({ + 'router:contentObject': initNavigationView, + 'app:languageChanged': initNavigationView + }); initNavigationView(); - Adapt.on('app:languageChanged', initNavigationView); }); data.on('loaded', async () => { diff --git a/less/devtools.less b/less/devtools.less index 5a5cdc4..c826f58 100644 --- a/less/devtools.less +++ b/less/devtools.less @@ -1,10 +1,4 @@ .devtools { - // Nav icons - // -------------------------------------------------- - &__nav-btn .icon { - .icon-cog; - } - // Items // -------------------------------------------------- &__item { diff --git a/properties.schema b/properties.schema index cc30ca6..6aa1046 100644 --- a/properties.schema +++ b/properties.schema @@ -1,26 +1,26 @@ { - "type":"object", + "type": "object", "$schema": "http://json-schema.org/draft-04/schema", "id": "http://jsonschema.net", - "required":false, - "properties":{ + "required": false, + "properties": { "pluginLocations": { - "type":"object", - "required":true, - "properties":{ + "type": "object", + "required": true, + "properties": { "config": { - "type":"object", + "type": "object", "properties": { "_devtools": { - "type":"object", - "required":false, + "type": "object", + "required": false, "legend": "Dev tools", - "properties":{ + "properties": { "_isEnabled": { - "type":"boolean", - "required":false, + "type": "boolean", + "required": false, "title": "Show dev tools in nav bar", - "inputType": { "type": "Boolean", "options": [false, true]}, + "inputType": { "type": "Boolean", "options": [false, true] }, "validators": [], "help": "Set to true to show 'dev tools' in the navigation bar. Remember to disable/uninstall before going live!" } @@ -29,19 +29,19 @@ } }, "course": { - "type":"object" + "type": "object" }, "contentobject": { - "type":"object" + "type": "object" }, "article": { - "type":"object" + "type": "object" }, "block": { - "type":"object" + "type": "object" }, "component": { - "type":"object" + "type": "object" } } } diff --git a/schema/course.schema.json b/schema/course.schema.json new file mode 100644 index 0000000..844e3f1 --- /dev/null +++ b/schema/course.schema.json @@ -0,0 +1,87 @@ +{ + "$anchor": "devtools-course", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "$patch": { + "source": { + "$ref": "course" + }, + "with": { + "properties": { + "_globals": { + "type": "object", + "default": {}, + "properties": { + "_extensions": { + "type": "object", + "default": {}, + "properties": { + "_devtools": { + "type": "object", + "title": "Dev Tools", + "default": {}, + "properties": { + "_navButton": { + "type": "object", + "title": "Navigation button", + "default": {}, + "properties": { + "ariaLabel": { + "type": "string", + "title": "Navigation button aria label", + "default": "Developer tools", + "_adapt": { + "translatable": true + } + }, + "_navOrder": { + "type": "number", + "title": "Navigation bar order", + "description": "Determines the order in which the button is displayed in the navigation bar. Negative numbers (e.g. -100) are left-aligned. Positive numbers (e.g. 100) are right-aligned.", + "default": 9000 + }, + "_showLabel": { + "type": "boolean", + "title": "Enable navigation bar button label", + "default": true + }, + "navLabel": { + "type": "string", + "title": "Navigation bar button label", + "default": "Dev Tools", + "_adapt": { + "translatable": true + } + } + } + }, + "_navTooltip": { + "type": "object", + "title": "Navigation tooltip", + "default": {}, + "properties": { + "_isEnabled": { + "type": "boolean", + "title": "Enable tooltip for navigation button", + "default": true + }, + "text": { + "type": "string", + "title": "Tooltip text", + "default": "Developer tools", + "_adapt": { + "translatable": true + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/templates/devtoolsNavigation.hbs b/templates/devtoolsNavigation.hbs deleted file mode 100644 index b8532eb..0000000 --- a/templates/devtoolsNavigation.hbs +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/templates/devtoolsNavigationButton.jsx b/templates/devtoolsNavigationButton.jsx new file mode 100644 index 0000000..5a3be6a --- /dev/null +++ b/templates/devtoolsNavigationButton.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { classes, compile } from 'core/js/reactHelpers'; + +export default function DevtoolsNavigationButton(props) { + const { + text, + _iconClasses + } = props; + + return ( + <> +