diff options
author | dizzy <diosmosis@users.noreply.github.com> | 2022-03-04 20:56:15 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-04 20:56:15 +0300 |
commit | 27faa3d8c008c97556a3ef27decd706d34b79a1a (patch) | |
tree | 782fc64cd6a6e4cbd6da365f478a90d2237cc394 /plugins/Dashboard | |
parent | b774954679f99ef190b9ef71043529563f764946 (diff) |
[Vue] Migrate Dashboard directive/model to Vue (#18883)
* get outputted typings to be used when compiling other plugins and fix typescript issues in CorePluginsAdmin
* readd corehome umd
* fix typescript errors in ExampleVue plugin
* fix feedback typescript errors
* rebuild
* migrate branding controller and get to build
* fix issues and get to work
* rebuild
* fix notification scroll
* migrate smtp settings controller in coreadminhome
* get to work
* migrate js tracking code generator and get to build
* migrate image tracking code generator and get to build
* get to work in UI
* get UI tests to pass locally
* forgot to add files + rebuild vue
* update screenshots
* Show a summary of new features (#18065)
* Added "What is new" notification display, populated by a new event
* Removed test example event hook
* Added support for applying a link attribute to menu items, fixes layout issue for mobile with html menu items
* Updated UI test screenshots
* Revert accidental edit
* Hide the "What's new" icon if there are no new features to show
* Changed to use changes.json, track user last viewed, added ui test
* Fix UserManager unit tests broken by new ts_changes_viewed user field
* Moved getChanges to separate helper class, added unit test, added user view access check
* Updated to add new changes table and populate only on plugin update/install
* Added missing fixture class, updated UI screenshots
* Updated matomo font to add ringing bell and new releases icons
* Fix for integration test
* Reworked class structure, removed unnecessary angular directive, merged templates, other tidy ups
* built vue files
* built vue files
* Added null user check, missing table exception handling, show plugin name in change title, better handling of missing change fields
* Added sample changes file, moved UserChanges db code to changes model, added return type hints, better db error code handling, various other improvements
* Revert accidental UI screenshot commit
* Fix for incorrect link name parameter in sample changes, switched back to using $db->query for INSERT IGNORE
* Integration test fix, UI screenshot updates
* Test fix
* Added link styling, show CoreHome changes without plugin prefix in title
* Update UI test screenshot
* Added styles to the popover, added event for filtering changes
* Test fix
* UI test screenshot updates
Co-authored-by: sgiehl <stefan@matomo.org>
Co-authored-by: bx80 <bx80@users.noreply.github.com>
* Update test translation (#18531)
update a test failed XML
* updates all submodules (#18541)
Co-authored-by: diosmosis <diosmosis@users.noreply.github.com>
* Translations update from Hosted Weblate (#18529)
* Translated using Weblate (Greek)
Currently translated at 100.0% (162 of 162 strings)
Translation: Matomo/Plugin CoreAdminHome
Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/el/
[ci skip]
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Vasilis Lourdas <dev@lourdas.eu>
* Translated using Weblate (Chinese (Simplified))
Currently translated at 83.9% (136 of 162 strings)
Translation: Matomo/Plugin CoreAdminHome
Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/zh_Hans/
[ci skip]
Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (620 of 622 strings)
Translation: Matomo/Matomo Base
Translate-URL: https://hosted.weblate.org/projects/matomo/matomo-base/zh_Hans/
[ci skip]
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 刘韬 <lyuutau@outlook.com>
* Update translation files
Updated by "Squash Git commits" hook in Weblate.
Translation: Matomo/Plugin CoreAdminHome
Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/
[ci skip]
Co-authored-by: Vasilis Lourdas <dev@lourdas.eu>
Co-authored-by: 刘韬 <lyuutau@outlook.com>
* [Vue] migrate report export directive and popover (#18440)
* update files
* sidenav start
* make getRef a utility method
* tweak
* add return type
* finish converting side-nav directive
* starting on reporting menu conversion
* remove unused properties
* convert reporting pages service
* migrate report metadata store
* remove angularjs files
* migrating reporting pages store
* make store adapters more immutable
* get service adapters to work
* fix a UI test
* another html fix
* migrate most of reporting menu directive and model
* Use themed font family for input forms to override materialize.css styling
* rebuild vue
* add a missing div
* ui test fixes
* update styling
* get to build
* get to load in the UI w/o error
* clone result of functions
* fix compile issue
* migrate widget loader and get to load in UI
* rebuild vue
* migrate widgetcontainer
* migrate widget bydimension container
* migrate widget + add tooltips directive
* quick fix
* Updating version to 4.6.0
* loading in page
* update expected screenshot
* add wait just in case travis is slow
* fix ordering bug
* add another wait
* rebuild vue
* css tweak
* fix some bugs and tests
* undo screenshot changes
* Menus test passing locally
* [Vue] date picker viewDate property is not kept up to date (#18385)
* viewDate ref is not kept up to date
* rebuild corehome
* reporting menu subcategory items are meant to be normal links
* update some screenshots
* use innerText instead of text() since angularjs maintains newlines in HTML that vue does not add
* trigger angularjs digest after ajaxhelper request
* rebuild vue
* update screenshots, fix bug in link generation in reporting menu and allow syncing multiple screenshot regexes at a time
* undo box-shadow change for UI tests
* fix more issues & update more tests
* update some screenshots
* fix some tests
* rebuild CoreHome
* quick fix
* built vue files
* fix angularjs issue
* add comment
* update umd files
* 4.6.1-rc1
* 4.6.1
* fix field array title
* apply some pr feedback
* apply more pr feedback
* another fix
* tweak
* fix ng-change not executed before ng-model
* fix another set of issues
* fix another issue
* rebuild vue
* better ng-change/ng-model fix
* update some screenshots
* rebuild vue
* remove some TODOs
* initiate initial ng-change ONLY for site selectors where this behavior applies
* emit/broadcast on correct scope in wrapper
* rebuild vue
* fix some issues
* couple more fixes
* fix another title issue
* rebuild vue
* do not report on ajax errors in notifications if not logged in
* migrate reporting page and model
* rebuild vue
* create sites selector model adapter
* fix siteselector vue bug, initial site is only set if there is just one site available
* rebuild vue
* migrate plugin settings directive
* remove TODO
* migrate plugin filter directive
* migrate two more plugins directives
* migrate save button
* fix a bunch of bugs
* fix another widget bug
* allow change event name between angularjs and vue
* rebuild vue
* migrate plugin form directive
* get to work
* migrate select-on-focus directive and start migrating report-export directive
* finish migrating report export directive & popover component + create reusable function to create vue app and add globals to it
* rebuild vue
* remove angularjs files and move less contents to vue dir
* built vue files
* fix function signature
* fix vue warning
* fix ajax request race condition
* rebuild vue
* add new notification type "help" so the help notification is not cleared when clearing transient notifications
* fix some bugs and tests
* update screenshot
* update screenshot & fix a test
* allow using unminified jquery ui + fix bug in last fix
* fix error when enrichedheadline is used in modal
* add polyfill min.js
* remove two todos
* fix widget url logic
* update some screenshots and fix sanitization/escape issue
* update screenshots
* rebuild vue
* fix url location updating regression in MatomoUrl.updateLocation use
* submodule
* update screenshots and fix possible error in json parse
* built vue files
* Merge branch 'vue-period-selector-regression' into vue-reporting-menu
* rebuild vue
* use correct variable
* rebuild vue
* fix widget url logic
* segment parameter can be undefined now for some reason
* fix ngmodel binding in siteselector adapter (for last time hopefully)
* the original site selector only set the first site to the first site in the initial sites query if there was only one site in the entire matomo instance
* fix sitesmanager ui test failure
* fix usersettings test failure
* rebuild vue
* more siteselector tweaks.
* build CoreHome
* more siteselector tweaks.
* another siteselector issue
* update screenshots
* update screenshot and try to fix random failure
* fix some issues in widget.vue when containerid is specified
* fix couple tests
* fix several test failures
* fix string concat
* fix test failure
* extra change
* fix last change and random failure
* styling fix
* fix last fix
* real fix this time
* fix stray request
* proper fix
* update build files
* try to fix random failure
* do not submit form
* check for api errors in promise chain in ajaxhelper.ts
* force a digest after a location change
* use proper abortcontroller method instead of promise hack, have to add new polyfill + try to fix random test failure
* some UI test fixes
* fix some report export issues
* several save button fixes + make replace approximation in createAngularJsAdapter better
* apply after manual click triggering in savebutton
* add names to divs so they can still be queried as they were in angularjs
* rebuild vue
* now that format_metrics checkbox works, need to check it
* fix unintended changes
* updated expected screenshots
* update two more
* go back to previous format_metrics behavior in popover
Co-authored-by: Justin Velluppillai <justin@innocraft.com>
Co-authored-by: justinvelluppillai <justinvelluppillai@users.noreply.github.com>
Co-authored-by: Matthieu Aubry <mattab@users.noreply.github.com>
* [Vue] remove support in vue for FormField.allSettings (#18542)
* deprecate support in vue for FormField.allSettings since deep watching the property doesnt quite work
* built vue files
* update screenshots
* update screenshot
* Show a summary of new features (#18065)
* Added "What is new" notification display, populated by a new event
* Removed test example event hook
* Added support for applying a link attribute to menu items, fixes layout issue for mobile with html menu items
* Updated UI test screenshots
* Revert accidental edit
* Hide the "What's new" icon if there are no new features to show
* Changed to use changes.json, track user last viewed, added ui test
* Fix UserManager unit tests broken by new ts_changes_viewed user field
* Moved getChanges to separate helper class, added unit test, added user view access check
* Updated to add new changes table and populate only on plugin update/install
* Added missing fixture class, updated UI screenshots
* Updated matomo font to add ringing bell and new releases icons
* Fix for integration test
* Reworked class structure, removed unnecessary angular directive, merged templates, other tidy ups
* built vue files
* built vue files
* Added null user check, missing table exception handling, show plugin name in change title, better handling of missing change fields
* Added sample changes file, moved UserChanges db code to changes model, added return type hints, better db error code handling, various other improvements
* Revert accidental UI screenshot commit
* Fix for incorrect link name parameter in sample changes, switched back to using $db->query for INSERT IGNORE
* Integration test fix, UI screenshot updates
* Test fix
* Added link styling, show CoreHome changes without plugin prefix in title
* Update UI test screenshot
* Added styles to the popover, added event for filtering changes
* Test fix
* UI test screenshot updates
Co-authored-by: sgiehl <stefan@matomo.org>
Co-authored-by: bx80 <bx80@users.noreply.github.com>
* Update test translation (#18531)
update a test failed XML
* updates all submodules (#18541)
Co-authored-by: diosmosis <diosmosis@users.noreply.github.com>
* Translations update from Hosted Weblate (#18529)
* Translated using Weblate (Greek)
Currently translated at 100.0% (162 of 162 strings)
Translation: Matomo/Plugin CoreAdminHome
Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/el/
[ci skip]
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Vasilis Lourdas <dev@lourdas.eu>
* Translated using Weblate (Chinese (Simplified))
Currently translated at 83.9% (136 of 162 strings)
Translation: Matomo/Plugin CoreAdminHome
Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/zh_Hans/
[ci skip]
Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (620 of 622 strings)
Translation: Matomo/Matomo Base
Translate-URL: https://hosted.weblate.org/projects/matomo/matomo-base/zh_Hans/
[ci skip]
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 刘韬 <lyuutau@outlook.com>
* Update translation files
Updated by "Squash Git commits" hook in Weblate.
Translation: Matomo/Plugin CoreAdminHome
Translate-URL: https://hosted.weblate.org/projects/matomo/plugin-coreadminhome/
[ci skip]
Co-authored-by: Vasilis Lourdas <dev@lourdas.eu>
Co-authored-by: 刘韬 <lyuutau@outlook.com>
* [Vue] migrate report export directive and popover (#18440)
* update files
* sidenav start
* make getRef a utility method
* tweak
* add return type
* finish converting side-nav directive
* starting on reporting menu conversion
* remove unused properties
* convert reporting pages service
* migrate report metadata store
* remove angularjs files
* migrating reporting pages store
* make store adapters more immutable
* get service adapters to work
* fix a UI test
* another html fix
* migrate most of reporting menu directive and model
* Use themed font family for input forms to override materialize.css styling
* rebuild vue
* add a missing div
* ui test fixes
* update styling
* get to build
* get to load in the UI w/o error
* clone result of functions
* fix compile issue
* migrate widget loader and get to load in UI
* rebuild vue
* migrate widgetcontainer
* migrate widget bydimension container
* migrate widget + add tooltips directive
* quick fix
* Updating version to 4.6.0
* loading in page
* update expected screenshot
* add wait just in case travis is slow
* fix ordering bug
* add another wait
* rebuild vue
* css tweak
* fix some bugs and tests
* undo screenshot changes
* Menus test passing locally
* [Vue] date picker viewDate property is not kept up to date (#18385)
* viewDate ref is not kept up to date
* rebuild corehome
* reporting menu subcategory items are meant to be normal links
* update some screenshots
* use innerText instead of text() since angularjs maintains newlines in HTML that vue does not add
* trigger angularjs digest after ajaxhelper request
* rebuild vue
* update screenshots, fix bug in link generation in reporting menu and allow syncing multiple screenshot regexes at a time
* undo box-shadow change for UI tests
* fix more issues & update more tests
* update some screenshots
* fix some tests
* rebuild CoreHome
* quick fix
* built vue files
* fix angularjs issue
* add comment
* update umd files
* 4.6.1-rc1
* 4.6.1
* fix field array title
* apply some pr feedback
* apply more pr feedback
* another fix
* tweak
* fix ng-change not executed before ng-model
* fix another set of issues
* fix another issue
* rebuild vue
* better ng-change/ng-model fix
* update some screenshots
* rebuild vue
* remove some TODOs
* initiate initial ng-change ONLY for site selectors where this behavior applies
* emit/broadcast on correct scope in wrapper
* rebuild vue
* fix some issues
* couple more fixes
* fix another title issue
* rebuild vue
* do not report on ajax errors in notifications if not logged in
* migrate reporting page and model
* rebuild vue
* create sites selector model adapter
* fix siteselector vue bug, initial site is only set if there is just one site available
* rebuild vue
* migrate plugin settings directive
* remove TODO
* migrate plugin filter directive
* migrate two more plugins directives
* migrate save button
* fix a bunch of bugs
* fix another widget bug
* allow change event name between angularjs and vue
* rebuild vue
* migrate plugin form directive
* get to work
* migrate select-on-focus directive and start migrating report-export directive
* finish migrating report export directive & popover component + create reusable function to create vue app and add globals to it
* rebuild vue
* remove angularjs files and move less contents to vue dir
* built vue files
* fix function signature
* fix vue warning
* fix ajax request race condition
* rebuild vue
* add new notification type "help" so the help notification is not cleared when clearing transient notifications
* fix some bugs and tests
* update screenshot
* update screenshot & fix a test
* allow using unminified jquery ui + fix bug in last fix
* fix error when enrichedheadline is used in modal
* add polyfill min.js
* remove two todos
* fix widget url logic
* update some screenshots and fix sanitization/escape issue
* update screenshots
* rebuild vue
* fix url location updating regression in MatomoUrl.updateLocation use
* submodule
* update screenshots and fix possible error in json parse
* built vue files
* Merge branch 'vue-period-selector-regression' into vue-reporting-menu
* rebuild vue
* use correct variable
* rebuild vue
* fix widget url logic
* segment parameter can be undefined now for some reason
* fix ngmodel binding in siteselector adapter (for last time hopefully)
* the original site selector only set the first site to the first site in the initial sites query if there was only one site in the entire matomo instance
* fix sitesmanager ui test failure
* fix usersettings test failure
* rebuild vue
* more siteselector tweaks.
* build CoreHome
* more siteselector tweaks.
* another siteselector issue
* update screenshots
* update screenshot and try to fix random failure
* fix some issues in widget.vue when containerid is specified
* fix couple tests
* fix several test failures
* fix string concat
* fix test failure
* extra change
* fix last change and random failure
* styling fix
* fix last fix
* real fix this time
* fix stray request
* proper fix
* update build files
* try to fix random failure
* do not submit form
* check for api errors in promise chain in ajaxhelper.ts
* force a digest after a location change
* use proper abortcontroller method instead of promise hack, have to add new polyfill + try to fix random test failure
* some UI test fixes
* fix some report export issues
* several save button fixes + make replace approximation in createAngularJsAdapter better
* apply after manual click triggering in savebutton
* add names to divs so they can still be queried as they were in angularjs
* rebuild vue
* now that format_metrics checkbox works, need to check it
* fix unintended changes
* updated expected screenshots
* update two more
* go back to previous format_metrics behavior in popover
Co-authored-by: Justin Velluppillai <justin@innocraft.com>
Co-authored-by: justinvelluppillai <justinvelluppillai@users.noreply.github.com>
Co-authored-by: Matthieu Aubry <mattab@users.noreply.github.com>
* [Vue] remove support in vue for FormField.allSettings (#18542)
* deprecate support in vue for FormField.allSettings since deep watching the property doesnt quite work
* built vue files
* update screenshots
* update screenshot
* fix tests
* rebuild
* rebuild
* order plugins by dependencies in vue:build and fix warning in corehome build
* built vue files
* built vue files
* remove unused imports
* built vue files
* remove multilinefield component, fieldtextareaarray does the same thing
* edit-trigger is not used anywhere
* migrate sitetypes model to store
* do not load nonexistant files
* remove reference nonexistant files
* start converting sitefields component
* more work on sitefields component
* undo submodule change
* rebuild
* get sitesmanager to build
* get SiteFields component to work in UI
* datepicker does not format times
* export other stores
* fix some typing issues and rebuild
* start on site management conversion
* add more comma delimited props to list + remove controller JS
* rebuild
* convert sites manager controller to sitesmanagement component
* remove TODOs
* finish migrating sitesmanager
* remove some TODO
* get to build
* fixes from testing
* rebuild
* rebuild and fix issue w/ globalsettings hash detection
* migrate capabilities-edit component.
* some fixes and get to build
* get to work
* built vue files
* get to work and rebuild
* migrate user edit form component
* some fixes
* fixes
* another fix
* more fixes
* update file
* more fixes
* fix ref
* rebuild vue
* couple more fixes
* migrate paged users list and get to build
* fixing issues
* workaround vue issue w/ directives that modify css classes on elements that also bind to :class
* dropdownmenu directive should be aware of data-target parameter that is required by materialize
* handle disabled options in fieldselect
* fix issues and rebuild vue
* migrate usersmanager component and get to build
* forgot to add files, fix some issues + rebuild
* migrate usersmanager controllers and twig template parts
* fix compile issues and get to build
* fix issues and rebuild
* fix bug and rebuild
* fix bug and rebuild
* fix issue
* fix issues and rebuild
* fix ui test
* fix UI test failure
* fixing some issues
* complete fixes
* fix some more issues
* fix ui test failures
* another fix
* several more fixes
* fix delete dialog
* more fixes
* fix styling issue
* more fixes
* fix another ui test + update other UI tests
* fixing edisiteid handling
* update screenshots
* fix UI tests somre more
* fix random failure
* fixes
* reference css class not attribute (since that is what is added in vue)
* fixing more ui tests
* try to fix vue css class in directive issue
* tweak
* in groupedsetting handle templateFile property for angularjs BC
* rebuild vue
* fix view tracking code link
* fixing UI tests
* fix selector in test for this branch only
* Update screenshot.
* update screenshot
* update screenshots
* style fix
* fix selectors and update screenshot
* built vue files
* Update screenshot + fix title and spacing.
* fix password changing
* fixing tests
* fix more issues
* fix styling
* built vue files
* more fixes
* more styling fixes
* more fixes
* Fix tests locally.
* Fixing more issues + getting UI tests to pass locally.
* update tagmanger module?
* fix UI tests
* remove unneeded event
* update screenshots
* start migrating series-picker
* get series picker component to work
* start migrating single metric view
* update style
* fix some issues
* get to work in UI
* fix percent evolution
* more migrating
* more changes
* migrate dashboard angularjs directives and get to work in the UI
* fix ajax loading race condition
* fix scope.fetchDashboard call and ui test random failures (hopefully)
* try fixing travis-ci failures
* rebuild
* rebuild CoreHome
* undo test change and fix another race condition
* remove TODO
* Update plugins/Dashboard/tests/UI/Dashboard_spec.js
Co-authored-by: Stefan Giehl <stefan@matomo.org>
Co-authored-by: Ben Burgess <88810029+bx80@users.noreply.github.com>
Co-authored-by: sgiehl <stefan@matomo.org>
Co-authored-by: bx80 <bx80@users.noreply.github.com>
Co-authored-by: Peter Zhang <peter@innocraft.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Weblate (bot) <hosted@weblate.org>
Co-authored-by: Vasilis Lourdas <dev@lourdas.eu>
Co-authored-by: 刘韬 <lyuutau@outlook.com>
Co-authored-by: Justin Velluppillai <justin@innocraft.com>
Co-authored-by: justinvelluppillai <justinvelluppillai@users.noreply.github.com>
Co-authored-by: Matthieu Aubry <mattab@users.noreply.github.com>
Diffstat (limited to 'plugins/Dashboard')
17 files changed, 820 insertions, 212 deletions
diff --git a/plugins/Dashboard/Dashboard.php b/plugins/Dashboard/Dashboard.php index 652bad1b4f..50898f119c 100644 --- a/plugins/Dashboard/Dashboard.php +++ b/plugins/Dashboard/Dashboard.php @@ -288,12 +288,10 @@ class Dashboard extends \Piwik\Plugin public function getJsFiles(&$jsFiles) { - $jsFiles[] = "plugins/Dashboard/angularjs/common/services/dashboards-model.js"; $jsFiles[] = "plugins/Dashboard/javascripts/widgetMenu.js"; $jsFiles[] = "plugins/Dashboard/javascripts/dashboardObject.js"; $jsFiles[] = "plugins/Dashboard/javascripts/dashboardWidget.js"; $jsFiles[] = "plugins/Dashboard/javascripts/dashboard.js"; - $jsFiles[] = "plugins/Dashboard/angularjs/dashboard/dashboard.directive.js"; } public function getStylesheetFiles(&$stylesheets) diff --git a/plugins/Dashboard/angularjs/common/services/dashboards-model.js b/plugins/Dashboard/angularjs/common/services/dashboards-model.js deleted file mode 100644 index 710e57220f..0000000000 --- a/plugins/Dashboard/angularjs/common/services/dashboards-model.js +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ -(function () { - angular.module('piwikApp.service').factory('dashboardsModel', dashboardsModel); - - dashboardsModel.$inject = ['piwikApi']; - - function dashboardsModel (piwikApi) { - - var dashboardsPromise = null; - - var model = { - dashboards: [], - getAllDashboards: getAllDashboards, - reloadAllDashboards: reloadAllDashboards, - getDashboard: getDashboard, - getDashboardLayout: getDashboardLayout - }; - - return model; - - function getDashboard(dashboardId) - { - return getAllDashboards().then(function (dashboards) { - var dashboard = null; - angular.forEach(dashboards, function (board) { - if (parseInt(board.id, 10) === parseInt(dashboardId, 10)) { - dashboard = board; - } - }); - return dashboard; - }); - } - - function getDashboardLayout(dashboardId) - { - piwikApi.withTokenInUrl(); - - return piwikApi.fetch({module: 'Dashboard', action: 'getDashboardLayout', idDashboard: dashboardId}); - } - - function reloadAllDashboards() - { - if (dashboardsPromise) { - dashboardsPromise = null; - } - - return getAllDashboards(); - } - - function getAllDashboards() - { - if (!dashboardsPromise) { - dashboardsPromise = piwikApi.fetch({method: 'Dashboard.getDashboards', filter_limit: '-1'}).then(function (response) { - if (response) { - model.dashboards = response; - } - - return response; - }); - } - - return dashboardsPromise; - } - } -})();
\ No newline at end of file diff --git a/plugins/Dashboard/angularjs/dashboard/dashboard.directive.js b/plugins/Dashboard/angularjs/dashboard/dashboard.directive.js deleted file mode 100644 index 092e314140..0000000000 --- a/plugins/Dashboard/angularjs/dashboard/dashboard.directive.js +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -/** - * <div piwik-dashboard dashboard-id="5"></div> - */ -(function () { - angular.module('piwikApp').directive('piwikDashboard', piwikDashboard); - - piwikDashboard.$inject = ['dashboardsModel', '$rootScope', '$q']; - - function piwikDashboard(dashboardsModel, $rootScope, $q) { - - function renderDashboard(dashboardId, dashboard, layout) - { - $('.dashboardSettings').show(); - initTopControls(); - - // Embed dashboard / exported as widget - if (!$('#topBars').length) { - $('.dashboardSettings').after($('#Dashboard')); - $('#Dashboard ul li').removeClass('active'); - $('#Dashboard_embeddedIndex_' + dashboardId).addClass('active'); - } - - widgetsHelper.getAvailableWidgets(); - - $('#dashboardWidgetsArea').off('dashboardempty', showEmptyDashboardNotification); - $('#dashboardWidgetsArea') - .on('dashboardempty', showEmptyDashboardNotification) - .dashboard({ - idDashboard: dashboardId, - layout: layout, - name: dashboard ? dashboard.name : '' - }); - - var divElements = $('#columnPreview').find('>div'); - - divElements.each(function () { - var width = []; - $('div', this).each(function () { - width.push(this.className.replace(/width-/, '')); - }); - $(this).attr('layout', width.join('-')); - }); - - divElements.off('click.renderDashboard'); - divElements.on('click.renderDashboard', function () { - divElements.removeClass('choosen'); - $(this).addClass('choosen'); - }); - } - - function fetchDashboard(dashboardId) { - var dashboardElement = $('#dashboardWidgetsArea'); - dashboardElement.dashboard('destroyWidgets'); - dashboardElement.empty(); - globalAjaxQueue.abort(); - - var getDashboard = dashboardsModel.getDashboard(dashboardId); - var getLayout = dashboardsModel.getDashboardLayout(dashboardId); - - return $q.all([getDashboard, getLayout]).then(function (response) { - var dashboard = response[0]; - var layout = response[1]; - - $(function() { - renderDashboard(dashboardId, dashboard, layout); - }); - }); - } - - function clearDashboard () { - $('.top_controls .dashboard-manager').hide(); - $('#dashboardWidgetsArea').dashboard('destroy'); - } - - return { - restrict: 'A', - scope: { - dashboardid: '=', - layout: '=' - }, - link: function (scope, element, attrs) { - - scope.$parent.fetchDashboard = function (dashboardId) { - scope.dashboardId = dashboardId; - return fetchDashboard(dashboardId) - }; - - fetchDashboard(scope.dashboardid); - - function onLocationChange(event, newUrl, oldUrl) - { - if (broadcast.getValueFromUrl('module') != 'Widgetize' && newUrl !== oldUrl && - newUrl.indexOf('category=Dashboard_Dashboard') === -1) { - // we remove the dashboard only if we no longer show a dashboard. - clearDashboard(); - } - } - - // should be rather handled in route or so. - var unbind = $rootScope.$on('$locationChangeSuccess', onLocationChange); - scope.$on('$destroy', onLocationChange); - scope.$on('$destroy', unbind); - } - }; - } -})();
\ No newline at end of file diff --git a/plugins/Dashboard/javascripts/dashboard.js b/plugins/Dashboard/javascripts/dashboard.js index 7af6e407b5..cd7c62d5f6 100644 --- a/plugins/Dashboard/javascripts/dashboard.js +++ b/plugins/Dashboard/javascripts/dashboard.js @@ -28,9 +28,9 @@ function createDashboard() { ajaxRequest.setCallback( function (response) { var id = response.value; - angular.element(document).injector().invoke(function ($location, reportingMenuModel, dashboardsModel) { + angular.element(document).injector().invoke(function ($location, reportingMenuModel) { Promise.all([ - dashboardsModel.reloadAllDashboards(), + Dashboard.DashboardStore.reloadAllDashboards(), reportingMenuModel.reloadMenuItems(), ]).then(function () { $('#dashboardWidgetsArea').dashboard('loadDashboard', id); @@ -86,7 +86,9 @@ function showChangeDashboardLayoutDialog() { function showEmptyDashboardNotification() { piwikHelper.modalConfirm(makeSelectorLastId('dashboardEmptyNotification'), { resetDashboard: function () { $('#dashboardWidgetsArea').dashboard('resetLayout'); }, - addWidget: function () { $('.dashboardSettings > a').trigger('click'); } + addWidget: function () { + $('.dashboardSettings > a').trigger('click'); + } }); } diff --git a/plugins/Dashboard/javascripts/dashboardObject.js b/plugins/Dashboard/javascripts/dashboardObject.js index d7c20ca25b..1308d1a7a3 100644 --- a/plugins/Dashboard/javascripts/dashboardObject.js +++ b/plugins/Dashboard/javascripts/dashboardObject.js @@ -94,9 +94,7 @@ $location.search('subcategory', '' + dashboardIdToLoad); }); } else { - var element = $('[piwik-dashboard]'); - var scope = angular.element(element).scope(); - scope.fetchDashboard(dashboardIdToLoad); + piwik.postEvent('Dashboard.loadDashboard', dashboardIdToLoad); } return this; diff --git a/plugins/Dashboard/javascripts/widgetMenu.js b/plugins/Dashboard/javascripts/widgetMenu.js index 25d4a779d7..f79365c6a1 100644 --- a/plugins/Dashboard/javascripts/widgetMenu.js +++ b/plugins/Dashboard/javascripts/widgetMenu.js @@ -8,6 +8,11 @@ function widgetsHelper() { } +// a Promise for the first call to getAvailableWidgets. this should not be aborted, +// so any code that aborts all ajax requests should make sure this promise is resolved +// first. +widgetsHelper.firstGetAvailableWidgetsCall = null; + /** * Returns the available widgets fetched via AJAX (if not already done) * @@ -61,41 +66,50 @@ widgetsHelper.getAvailableWidgets = function (callback) { return moved; } - if (!widgetsHelper.availableWidgets) { + var promise = new Promise(function (resolve, reject) { + if (!widgetsHelper.availableWidgets) { var ajaxRequest = new ajaxHelper(); ajaxRequest._mixinDefaultGetParams = function (params) { - return params; + return params; }; ajaxRequest.addParams({ - module: 'API', - method: 'API.getWidgetMetadata', - filter_limit: '-1', - format: 'JSON', - deep: '1', - idSite: piwik.idSite || broadcast.getValueFromUrl('idSite') + module: 'API', + method: 'API.getWidgetMetadata', + filter_limit: '-1', + format: 'JSON', + deep: '1', + idSite: piwik.idSite || broadcast.getValueFromUrl('idSite') }, 'get'); ajaxRequest.setCallback( - function (data) { - widgetsHelper.availableWidgets = mergeCategoriesAndSubCategories(data); + function (data) { + widgetsHelper.availableWidgets = mergeCategoriesAndSubCategories(data); - if (callback) { - callback(widgetsHelper.availableWidgets); - } - } + resolve(); + } ); ajaxRequest.setErrorCallback(function (deferred, status) { - if (status == 'abort' || !deferred || deferred.status < 400 || deferred.status >= 600) { - return; - } - $('#loadingError').show(); + if (status == 'abort' || !deferred || deferred.status < 400 || deferred.status >= 600) { + return; + } + $('#loadingError').show(); + reject(); }); ajaxRequest.send(); return; + } + + resolve(); + }); + + if (!widgetsHelper.firstGetAvailableWidgetsCall) { + widgetsHelper.firstGetAvailableWidgetsCall = promise; } - if (callback) { + promise.then(function () { + if (callback) { callback(widgetsHelper.availableWidgets); - } + } + }); }; /** diff --git a/plugins/Dashboard/tests/UI/DashboardManager_spec.js b/plugins/Dashboard/tests/UI/DashboardManager_spec.js index 4fc6b9d64a..fe6e1d7b13 100644 --- a/plugins/Dashboard/tests/UI/DashboardManager_spec.js +++ b/plugins/Dashboard/tests/UI/DashboardManager_spec.js @@ -109,6 +109,9 @@ describe("DashboardManager", function () { await page.waitForTimeout(500); await page.waitForNetworkIdle(); + await page.waitForSelector('.widget'); + await page.waitForNetworkIdle(); + expect(await page.screenshot({ fullPage: true })).to.matchImage('removed'); }); }); diff --git a/plugins/Dashboard/tests/UI/Dashboard_spec.js b/plugins/Dashboard/tests/UI/Dashboard_spec.js index 269f6e4517..2140504911 100644 --- a/plugins/Dashboard/tests/UI/Dashboard_spec.js +++ b/plugins/Dashboard/tests/UI/Dashboard_spec.js @@ -130,6 +130,8 @@ describe("Dashboard", function () { it("should add a widget when a widget is selected in the dashboard manager", async function() { await page.click('.dashboard-manager .title'); + await page.waitForSelector('.widgetpreview-categorylist>li'); + var live = await page.jQuery('.widgetpreview-categorylist>li:contains(Goals)'); // have to mouse move twice... otherwise Live! will just be highlighted await live.hover(); await live.click(); @@ -233,6 +235,8 @@ describe("Dashboard", function () { await button.click(); await page.waitForNetworkIdle(); await page.mouse.move(-10, -10); + await page.waitForSelector('.widget'); + await page.waitForNetworkIdle(); expect(await page.screenshot({ fullPage: true })).to.matchImage('reset'); }); @@ -246,6 +250,8 @@ describe("Dashboard", function () { await page.mouse.move(-10, -10); await page.waitForTimeout(200); await page.waitForNetworkIdle(); + await page.waitForSelector('.widget'); + await page.waitForNetworkIdle(); expect(await page.screenshot({ fullPage: true })).to.matchImage('removed'); }); @@ -288,11 +294,11 @@ describe("Dashboard", function () { var button = await page.jQuery('.modal.open .modal-footer a:contains(Ok)'); await button.click(); await page.mouse.move(-10, -10); + await page.waitForSelector('.widget'); await page.waitForNetworkIdle(); expect(await page.screenshot({ fullPage: true })).to.matchImage('create_new'); }); - it("should load segmented dashboard", async function() { await removeAllExtraDashboards(); await page.goto(url + '&segment=' + encodeURIComponent("browserCode==FF")); diff --git a/plugins/Dashboard/vue/dist/Dashboard.umd.js b/plugins/Dashboard/vue/dist/Dashboard.umd.js new file mode 100644 index 0000000000..37eff018a6 --- /dev/null +++ b/plugins/Dashboard/vue/dist/Dashboard.umd.js @@ -0,0 +1,431 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("CoreHome"), require("vue")); + else if(typeof define === 'function' && define.amd) + define(["CoreHome", ], factory); + else if(typeof exports === 'object') + exports["Dashboard"] = factory(require("CoreHome"), require("vue")); + else + root["Dashboard"] = factory(root["CoreHome"], root["Vue"]); +})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__, __WEBPACK_EXTERNAL_MODULE__8bbf__) { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "plugins/Dashboard/vue/dist/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "fae3"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "19dc": +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__; + +/***/ }), + +/***/ "8bbf": +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE__8bbf__; + +/***/ }), + +/***/ "fae3": +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +// ESM COMPAT FLAG +__webpack_require__.r(__webpack_exports__); + +// EXPORTS +__webpack_require__.d(__webpack_exports__, "DashboardStore", function() { return /* reexport */ Dashboard_store; }); +__webpack_require__.d(__webpack_exports__, "Dashboard", function() { return /* reexport */ Dashboard; }); + +// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js +// This file is imported into lib/wc client bundles. + +if (typeof window !== 'undefined') { + var currentScript = window.document.currentScript + if (false) { var getCurrentScript; } + + var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/) + if (src) { + __webpack_require__.p = src[1] // eslint-disable-line + } +} + +// Indicate to webpack that this file can be concatenated +/* harmony default export */ var setPublicPath = (null); + +// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"} +var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf"); + +// EXTERNAL MODULE: external "CoreHome" +var external_CoreHome_ = __webpack_require__("19dc"); + +// CONCATENATED MODULE: ./plugins/Dashboard/vue/src/Dashboard/Dashboard.store.ts +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } + +function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + + +var Dashboard_store_DashboardStore = /*#__PURE__*/function () { + function DashboardStore() { + var _this = this; + + _classCallCheck(this, DashboardStore); + + _defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({ + dashboards: [] + })); + + _defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(_this.privateState); + })); + + _defineProperty(this, "dashboards", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(function () { + return _this.state.value.dashboards; + })); + + _defineProperty(this, "dashboardsPromise", null); + } + + _createClass(DashboardStore, [{ + key: "getDashboard", + value: function getDashboard(dashboardId) { + return this.getAllDashboards().then(function (dashboards) { + return dashboards.find(function (b) { + return parseInt("".concat(b.id), 10) === parseInt("".concat(dashboardId), 10); + }); + }); + } + }, { + key: "getDashboardLayout", + value: function getDashboardLayout(dashboardId) { + return external_CoreHome_["AjaxHelper"].fetch({ + module: 'Dashboard', + action: 'getDashboardLayout', + idDashboard: dashboardId + }, { + withTokenInUrl: true + }); + } + }, { + key: "reloadAllDashboards", + value: function reloadAllDashboards() { + this.dashboardsPromise = null; + return this.getAllDashboards(); + } + }, { + key: "getAllDashboards", + value: function getAllDashboards() { + var _this2 = this; + + if (!this.dashboardsPromise) { + this.dashboardsPromise = external_CoreHome_["AjaxHelper"].fetch({ + method: 'Dashboard.getDashboards', + filter_limit: '-1' + }).then(function (response) { + if (response) { + _this2.privateState.dashboards = response; + } + + return _this2.dashboards.value; + }); + } + + return this.dashboardsPromise; + } + }]); + + return DashboardStore; +}(); + +/* harmony default export */ var Dashboard_store = (new Dashboard_store_DashboardStore()); +// CONCATENATED MODULE: ./plugins/Dashboard/vue/src/Dashboard/Dashboard.store.adapter.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +window.angular.module('piwikApp.service').factory('dashboardsModel', function () { + return Dashboard_store; +}); +// CONCATENATED MODULE: ./plugins/Dashboard/vue/src/Dashboard/Dashboard.ts +function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } + +function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } + +function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } + +function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } + +function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } + +function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } + +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + + +var _window = window, + $ = _window.$; + +function renderDashboard(dashboardId, dashboard, layout) { + var $settings = $('.dashboardSettings'); + $settings.show(); + window.initTopControls(); // Embed dashboard / exported as widget + + if (!$('#topBars').length) { + $settings.after($('#Dashboard')); + $('#Dashboard ul li').removeClass('active'); + $("#Dashboard_embeddedIndex_".concat(dashboardId)).addClass('active'); + } + + window.widgetsHelper.getAvailableWidgets(); // eslint-disable-next-line @typescript-eslint/no-explicit-any + + $('#dashboardWidgetsArea').off('dashboardempty', window.showEmptyDashboardNotification).on('dashboardempty', window.showEmptyDashboardNotification).dashboard({ + idDashboard: dashboardId, + layout: layout, + name: dashboard ? dashboard.name : '' + }); + var divElements = $('#columnPreview').find('>div'); + divElements.each(function eachPreview() { + var width = []; + $('div', this).each(function eachDiv() { + width.push(this.className.replace(/width-/, '')); + }); + $(this).attr('layout', width.join('-')); + }); + divElements.off('click.renderDashboard'); + divElements.on('click.renderDashboard', function onRenderDashboard() { + divElements.removeClass('choosen'); + $(this).addClass('choosen'); + }); +} + +function fetchDashboard(dashboardId) { + window.globalAjaxQueue.abort(); + return new Promise(function (resolve) { + return setTimeout(resolve); + }).then(function () { + return Promise.resolve(window.widgetsHelper.firstGetAvailableWidgetsCall); + }).then(function () { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var dashboardElement = $('#dashboardWidgetsArea'); + dashboardElement.dashboard('destroyWidgets'); + dashboardElement.empty(); + return Promise.all([Dashboard_store.getDashboard(dashboardId), Dashboard_store.getDashboardLayout(dashboardId)]); + }).then(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + dashboard = _ref2[0], + layout = _ref2[1]; + + return new Promise(function (resolve) { + $(function () { + renderDashboard(dashboardId, dashboard, layout); + resolve(); + }); + }); + }); +} + +function clearDashboard() { + $('.top_controls .dashboard-manager').hide(); // eslint-disable-next-line @typescript-eslint/no-explicit-any + + $('#dashboardWidgetsArea').dashboard('destroy'); +} + +function onLocationChange(parsed) { + if (parsed.module !== 'Widgetize' && parsed.category !== 'Dashboard_Dashboard') { + // we remove the dashboard only if we no longer show a dashboard. + clearDashboard(); + } +} + +function onLoadPage(params) { + if (params.category === 'Dashboard_Dashboard' && $.isNumeric(params.subcategory)) { + params.promise = fetchDashboard(parseInt(params.subcategory, 10)); + } +} + +function onLoadDashboard(idDashboard) { + fetchDashboard(idDashboard); +} + +/* harmony default export */ var Dashboard = ({ + mounted: function mounted(el, binding) { + fetchDashboard(binding.value.idDashboard); + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["watch"])(function () { + return external_CoreHome_["MatomoUrl"].parsed.value; + }, function (parsed) { + onLocationChange(parsed); + }); // load dashboard directly since it will be faster than going through reporting page API + + external_CoreHome_["Matomo"].on('ReportingPage.loadPage', onLoadPage); + external_CoreHome_["Matomo"].on('Dashboard.loadDashboard', onLoadDashboard); + }, + unmounted: function unmounted() { + onLocationChange(external_CoreHome_["MatomoUrl"].parsed.value); + external_CoreHome_["Matomo"].off('ReportingPage.loadPage', onLoadPage); + external_CoreHome_["Matomo"].off('Dashboard.loadDashboard', onLoadDashboard); + } +}); +// CONCATENATED MODULE: ./plugins/Dashboard/vue/src/Dashboard/Dashboard.adapter.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +function piwikDashboard() { + return { + restrict: 'A', + scope: { + dashboardid: '=', + layout: '=' + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + link: function expandOnClickLink(scope, element) { + var binding = { + instance: null, + value: { + idDashboard: scope.dashboardid, + layout: scope.layout + }, + oldValue: null, + modifiers: {}, + dir: {} + }; + Dashboard.mounted(element[0], binding); // using scope destroy instead of element destroy event, since piwik-dashboard elements + // are removed manually, outside of angularjs/vue workflow, so element destroy is not + // triggered + + scope.$on('$destroy', function () { + Dashboard.unmounted(); + }); + } + }; +} +window.angular.module('piwikApp').directive('piwikDashboard', piwikDashboard); +// CONCATENATED MODULE: ./plugins/Dashboard/vue/src/index.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + + + +// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js + + + + +/***/ }) + +/******/ }); +}); +//# sourceMappingURL=Dashboard.umd.js.map
\ No newline at end of file diff --git a/plugins/Dashboard/vue/dist/Dashboard.umd.min.js b/plugins/Dashboard/vue/dist/Dashboard.umd.min.js new file mode 100644 index 0000000000..15c79f8b71 --- /dev/null +++ b/plugins/Dashboard/vue/dist/Dashboard.umd.min.js @@ -0,0 +1,27 @@ +(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("CoreHome"),require("vue")):"function"===typeof define&&define.amd?define(["CoreHome"],t):"object"===typeof exports?exports["Dashboard"]=t(require("CoreHome"),require("vue")):e["Dashboard"]=t(e["CoreHome"],e["Vue"])})("undefined"!==typeof self?self:this,(function(e,t){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},r.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},r.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="plugins/Dashboard/vue/dist/",r(r.s="fae3")}({"19dc":function(t,r){t.exports=e},"8bbf":function(e,r){e.exports=t},fae3:function(e,t,r){"use strict";if(r.r(t),r.d(t,"DashboardStore",(function(){return f})),r.d(t,"Dashboard",(function(){return S})),"undefined"!==typeof window){var o=window.document.currentScript,n=o&&o.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);n&&(r.p=n[1])}var a=r("8bbf"),i=r("19dc");function u(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function d(e,t){for(var r=0;r<t.length;r++){var o=t[r];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}function s(e,t,r){return t&&d(e.prototype,t),r&&d(e,r),e}function l(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */var c=function(){function e(){var t=this;u(this,e),l(this,"privateState",Object(a["reactive"])({dashboards:[]})),l(this,"state",Object(a["computed"])((function(){return Object(a["readonly"])(t.privateState)}))),l(this,"dashboards",Object(a["computed"])((function(){return t.state.value.dashboards}))),l(this,"dashboardsPromise",null)}return s(e,[{key:"getDashboard",value:function(e){return this.getAllDashboards().then((function(t){return t.find((function(t){return parseInt("".concat(t.id),10)===parseInt("".concat(e),10)}))}))}},{key:"getDashboardLayout",value:function(e){return i["AjaxHelper"].fetch({module:"Dashboard",action:"getDashboardLayout",idDashboard:e},{withTokenInUrl:!0})}},{key:"reloadAllDashboards",value:function(){return this.dashboardsPromise=null,this.getAllDashboards()}},{key:"getAllDashboards",value:function(){var e=this;return this.dashboardsPromise||(this.dashboardsPromise=i["AjaxHelper"].fetch({method:"Dashboard.getDashboards",filter_limit:"-1"}).then((function(t){return t&&(e.privateState.dashboards=t),e.dashboards.value}))),this.dashboardsPromise}}]),e}(),f=new c;function h(e,t){return v(e)||y(e,t)||p(e,t)||b()}function b(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function p(e,t){if(e){if("string"===typeof e)return m(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?m(e,t):void 0}}function m(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,o=new Array(t);r<t;r++)o[r]=e[r];return o}function y(e,t){var r=null==e?null:"undefined"!==typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var o,n,a=[],i=!0,u=!1;try{for(r=r.call(e);!(i=(o=r.next()).done);i=!0)if(a.push(o.value),t&&a.length===t)break}catch(d){u=!0,n=d}finally{try{i||null==r["return"]||r["return"]()}finally{if(u)throw n}}return a}}function v(e){if(Array.isArray(e))return e} +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +window.angular.module("piwikApp.service").factory("dashboardsModel",(function(){return f}));var g=window,w=g.$;function D(e,t,r){var o=w(".dashboardSettings");o.show(),window.initTopControls(),w("#topBars").length||(o.after(w("#Dashboard")),w("#Dashboard ul li").removeClass("active"),w("#Dashboard_embeddedIndex_".concat(e)).addClass("active")),window.widgetsHelper.getAvailableWidgets(),w("#dashboardWidgetsArea").off("dashboardempty",window.showEmptyDashboardNotification).on("dashboardempty",window.showEmptyDashboardNotification).dashboard({idDashboard:e,layout:r,name:t?t.name:""});var n=w("#columnPreview").find(">div");n.each((function(){var e=[];w("div",this).each((function(){e.push(this.className.replace(/width-/,""))})),w(this).attr("layout",e.join("-"))})),n.off("click.renderDashboard"),n.on("click.renderDashboard",(function(){n.removeClass("choosen"),w(this).addClass("choosen")}))}function j(e){return window.globalAjaxQueue.abort(),new Promise((function(e){return setTimeout(e)})).then((function(){return Promise.resolve(window.widgetsHelper.firstGetAvailableWidgetsCall)})).then((function(){var t=w("#dashboardWidgetsArea");return t.dashboard("destroyWidgets"),t.empty(),Promise.all([f.getDashboard(e),f.getDashboardLayout(e)])})).then((function(t){var r=h(t,2),o=r[0],n=r[1];return new Promise((function(t){w((function(){D(e,o,n),t()}))}))}))}function A(){w(".top_controls .dashboard-manager").hide(),w("#dashboardWidgetsArea").dashboard("destroy")}function P(e){"Widgetize"!==e.module&&"Dashboard_Dashboard"!==e.category&&A()}function x(e){"Dashboard_Dashboard"===e.category&&w.isNumeric(e.subcategory)&&(e.promise=j(parseInt(e.subcategory,10)))}function O(e){j(e)}var S={mounted:function(e,t){j(t.value.idDashboard),Object(a["watch"])((function(){return i["MatomoUrl"].parsed.value}),(function(e){P(e)})),i["Matomo"].on("ReportingPage.loadPage",x),i["Matomo"].on("Dashboard.loadDashboard",O)},unmounted:function(){P(i["MatomoUrl"].parsed.value),i["Matomo"].off("ReportingPage.loadPage",x),i["Matomo"].off("Dashboard.loadDashboard",O)}}; +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */function k(){return{restrict:"A",scope:{dashboardid:"=",layout:"="},link:function(e,t){var r={instance:null,value:{idDashboard:e.dashboardid,layout:e.layout},oldValue:null,modifiers:{},dir:{}};S.mounted(t[0],r),e.$on("$destroy",(function(){S.unmounted()}))}}}window.angular.module("piwikApp").directive("piwikDashboard",k)}})})); +//# sourceMappingURL=Dashboard.umd.min.js.map
\ No newline at end of file diff --git a/plugins/Dashboard/vue/dist/umd.metadata.json b/plugins/Dashboard/vue/dist/umd.metadata.json new file mode 100644 index 0000000000..9ecfcc0456 --- /dev/null +++ b/plugins/Dashboard/vue/dist/umd.metadata.json @@ -0,0 +1,5 @@ +{ + "dependsOn": [ + "CoreHome" + ] +}
\ No newline at end of file diff --git a/plugins/Dashboard/vue/src/Dashboard/Dashboard.adapter.ts b/plugins/Dashboard/vue/src/Dashboard/Dashboard.adapter.ts new file mode 100644 index 0000000000..5f31fce6f9 --- /dev/null +++ b/plugins/Dashboard/vue/src/Dashboard/Dashboard.adapter.ts @@ -0,0 +1,44 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { IDirective } from 'angular'; +import Dashboard from './Dashboard'; +import { DashboardLayout } from '../types'; + +export default function piwikDashboard(): IDirective { + return { + restrict: 'A', + scope: { + dashboardid: '=', + layout: '=', + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + link: function expandOnClickLink(scope: any, element: JQuery) { + const binding = { + instance: null, + value: { + idDashboard: scope.dashboardid as string, + layout: scope.layout as DashboardLayout, + }, + oldValue: null, + modifiers: {}, + dir: {}, + }; + + Dashboard.mounted(element[0], binding); + + // using scope destroy instead of element destroy event, since piwik-dashboard elements + // are removed manually, outside of angularjs/vue workflow, so element destroy is not + // triggered + scope.$on('$destroy', () => { + Dashboard.unmounted(); + }); + }, + }; +} + +window.angular.module('piwikApp').directive('piwikDashboard', piwikDashboard); diff --git a/plugins/Dashboard/vue/src/Dashboard/Dashboard.store.adapter.ts b/plugins/Dashboard/vue/src/Dashboard/Dashboard.store.adapter.ts new file mode 100644 index 0000000000..0fd299225d --- /dev/null +++ b/plugins/Dashboard/vue/src/Dashboard/Dashboard.store.adapter.ts @@ -0,0 +1,10 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import DashboardStore from './Dashboard.store'; + +window.angular.module('piwikApp.service').factory('dashboardsModel', () => DashboardStore); diff --git a/plugins/Dashboard/vue/src/Dashboard/Dashboard.store.ts b/plugins/Dashboard/vue/src/Dashboard/Dashboard.store.ts new file mode 100644 index 0000000000..ed38275b37 --- /dev/null +++ b/plugins/Dashboard/vue/src/Dashboard/Dashboard.store.ts @@ -0,0 +1,76 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { + computed, + readonly, + reactive, + DeepReadonly, +} from 'vue'; +import { AjaxHelper } from 'CoreHome'; +import { DashboardLayout, Dashboard } from '../types'; + +interface DashboardStoreState { + dashboards: Dashboard[]; +} + +class DashboardStore { + private privateState = reactive<DashboardStoreState>({ + dashboards: [], + }); + + readonly state = computed(() => readonly(this.privateState)); + + readonly dashboards = computed(() => this.state.value.dashboards); + + private dashboardsPromise: Promise<DeepReadonly<Dashboard[]>>|null = null; + + getDashboard(dashboardId: string|number) { + return this.getAllDashboards().then( + (dashboards) => dashboards.find( + (b) => parseInt(`${b.id}`, 10) === parseInt(`${dashboardId}`, 10), + ), + ); + } + + getDashboardLayout(dashboardId: string|number): Promise<DashboardLayout> { + return AjaxHelper.fetch<DashboardLayout>( + { + module: 'Dashboard', + action: 'getDashboardLayout', + idDashboard: dashboardId, + }, + { + withTokenInUrl: true, + }, + ); + } + + reloadAllDashboards(): ReturnType<DashboardStore['getAllDashboards']> { + this.dashboardsPromise = null; + return this.getAllDashboards(); + } + + getAllDashboards(): Promise<DeepReadonly<Dashboard[]>> { + if (!this.dashboardsPromise) { + this.dashboardsPromise = AjaxHelper.fetch<Dashboard[]>({ + method: 'Dashboard.getDashboards', + filter_limit: '-1', + }).then((response) => { + if (response) { + this.privateState.dashboards = response; + } + + return this.dashboards.value; + }); + } + + return this.dashboardsPromise; + } +} + +export default new DashboardStore(); diff --git a/plugins/Dashboard/vue/src/Dashboard/Dashboard.ts b/plugins/Dashboard/vue/src/Dashboard/Dashboard.ts new file mode 100644 index 0000000000..d13c8ee3e2 --- /dev/null +++ b/plugins/Dashboard/vue/src/Dashboard/Dashboard.ts @@ -0,0 +1,137 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { DirectiveBinding, watch } from 'vue'; +import { Matomo, MatomoUrl } from 'CoreHome'; +import DashboardStore from './Dashboard.store'; +import { Dashboard, DashboardLayout } from '../types'; + +interface DashboardDirectiveArgs { + idDashboard: string|number; + layout?: unknown; +} + +const { $ } = window; + +function renderDashboard( + dashboardId: string|number, + dashboard: Dashboard, + layout: DashboardLayout, +) { + const $settings = $('.dashboardSettings'); + + $settings.show(); + window.initTopControls(); + + // Embed dashboard / exported as widget + if (!$('#topBars').length) { + $settings.after($('#Dashboard')); + $('#Dashboard ul li').removeClass('active'); + $(`#Dashboard_embeddedIndex_${dashboardId}`).addClass('active'); + } + + window.widgetsHelper.getAvailableWidgets(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ($('#dashboardWidgetsArea') as any) + .off('dashboardempty', window.showEmptyDashboardNotification) + .on('dashboardempty', window.showEmptyDashboardNotification) + .dashboard({ + idDashboard: dashboardId, + layout, + name: dashboard ? dashboard.name : '', + }); + + const divElements = $('#columnPreview').find('>div'); + divElements.each(function eachPreview() { + const width: string[] = []; + $('div', this).each(function eachDiv() { + width.push(this.className.replace(/width-/, '')); + }); + $(this).attr('layout', width.join('-')); + }); + + divElements.off('click.renderDashboard'); + divElements.on('click.renderDashboard', function onRenderDashboard() { + divElements.removeClass('choosen'); + $(this).addClass('choosen'); + }); +} + +function fetchDashboard(dashboardId: string|number) { + window.globalAjaxQueue.abort(); + + return new Promise((resolve) => setTimeout(resolve)).then( + () => Promise.resolve(window.widgetsHelper.firstGetAvailableWidgetsCall), + ).then(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const dashboardElement = $('#dashboardWidgetsArea') as any; + dashboardElement.dashboard('destroyWidgets'); + dashboardElement.empty(); + + return Promise.all([ + DashboardStore.getDashboard(dashboardId), + DashboardStore.getDashboardLayout(dashboardId), + ]); + }).then(([dashboard, layout]) => new Promise<void>((resolve) => { + $(() => { + renderDashboard(dashboardId, dashboard as Dashboard, layout as DashboardLayout); + resolve(); + }); + })); +} + +function clearDashboard() { + $('.top_controls .dashboard-manager').hide(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ($('#dashboardWidgetsArea') as any).dashboard('destroy'); +} + +function onLocationChange(parsed: (typeof MatomoUrl)['urlParsed']['value']) { + if (parsed.module !== 'Widgetize' && parsed.category !== 'Dashboard_Dashboard') { + // we remove the dashboard only if we no longer show a dashboard. + clearDashboard(); + } +} +interface LoadPageArgs { + category: string; + subcategory: string; + promise?: Promise<void>; +} + +function onLoadPage(params: LoadPageArgs) { + if (params.category === 'Dashboard_Dashboard' + && $.isNumeric(params.subcategory) + ) { + params.promise = fetchDashboard(parseInt(params.subcategory, 10)); + } +} + +function onLoadDashboard(idDashboard: string|number) { + fetchDashboard(idDashboard); +} + +export default { + mounted(el: HTMLElement, binding: DirectiveBinding<DashboardDirectiveArgs>): void { + fetchDashboard(binding.value.idDashboard); + + watch(() => MatomoUrl.parsed.value, (parsed) => { + onLocationChange(parsed); + }); + + // load dashboard directly since it will be faster than going through reporting page API + Matomo.on('ReportingPage.loadPage', onLoadPage); + + Matomo.on('Dashboard.loadDashboard', onLoadDashboard); + }, + unmounted(): void { + onLocationChange(MatomoUrl.parsed.value); + + Matomo.off('ReportingPage.loadPage', onLoadPage); + Matomo.off('Dashboard.loadDashboard', onLoadDashboard); + }, +}; diff --git a/plugins/Dashboard/vue/src/index.ts b/plugins/Dashboard/vue/src/index.ts new file mode 100644 index 0000000000..4d8b6c30d4 --- /dev/null +++ b/plugins/Dashboard/vue/src/index.ts @@ -0,0 +1,12 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import './Dashboard/Dashboard.store.adapter'; +import './Dashboard/Dashboard.adapter'; + +export { default as DashboardStore } from './Dashboard/Dashboard.store'; +export { default as Dashboard } from './Dashboard/Dashboard'; diff --git a/plugins/Dashboard/vue/src/types.ts b/plugins/Dashboard/vue/src/types.ts new file mode 100644 index 0000000000..ff25f40e6e --- /dev/null +++ b/plugins/Dashboard/vue/src/types.ts @@ -0,0 +1,28 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { WidgetType } from 'CoreHome'; + +interface WidgetRef { + module: string; + action: string; +} + +export interface Dashboard { + id: string|number; + name: string; + widgets: WidgetRef[]; +} + +interface DashboardLayoutConfig { + layout: string; +} + +export interface DashboardLayout { + columns: WidgetType[][]; + config: DashboardLayoutConfig; +} |