PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,49 @@
<div
class="jqplot-seriespicker"
ng-class="{open: $ctrl.isPopupVisible}"
ng-mouseenter="$ctrl.isPopupVisible = true"
ng-mouseleave="$ctrl.onLeavePopup()"
>
<a
href="#"
ng-click="$event.preventDefault(); $event.stopPropagation();"
>
+
</a>
<div
class="jqplot-seriespicker-popover"
ng-if="$ctrl.isPopupVisible"
>
<p class="headline">{{ ($ctrl.multiselect ? 'General_MetricsToPlot' : 'General_MetricToPlot') | translate }}</p>
<p
ng-repeat="columnConfig in $ctrl.selectableColumns"
class="pickColumn"
ng-click="$ctrl.optionSelected(columnConfig.column, $ctrl.columnStates)"
>
<input
class="select"
ng-checked="$ctrl.columnStates[columnConfig.column]"
ng-attr-type="{{ $ctrl.multiselect ? 'checkbox' : 'radio' }}"
/>
<label>{{ columnConfig.translation }}</label>
</p>
<p
ng-if="$ctrl.selectableRows.length"
class="headline recordsToPlot"
>
{{ 'General_RecordsToPlot' | translate }}
</p>
<p
ng-repeat="rowConfig in $ctrl.selectableRows"
class="pickRow"
ng-click="$ctrl.optionSelected(rowConfig.matcher, $ctrl.rowStates)"
>
<input
class="select"
ng-checked="$ctrl.rowStates[rowConfig.matcher]"
ng-attr-type="{{ $ctrl.multiselect ? 'checkbox' : 'radio' }}"
/>
<label>{{ rowConfig.label }}</label>
</p>
</div>
</div>

View File

@ -0,0 +1,144 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* This series picker component is a popup that displays a list of metrics/row
* values that can be selected. It's used by certain datatable visualizations
* to allow users to select different data series for display.
*
* Inputs:
* - multiselect: true if the picker should allow selecting multiple items, false
* if otherwise.
* - selectableColumns: the list of selectable metric values. must be a list of
* objects with the following properties:
* * column: the ID of the column, eg, nb_visits
* * translation: the translated text for the column, eg, Visits
* - selectableRows: the list of selectable row values. must be a list of objects
* with the following properties:
* * matcher: the ID of the row
* * label: the display text for the row
* - selectedColumns: the list of selected columns. should be a list of strings
* that correspond to the 'column' property in selectableColumns.
* - selectedRows: the list of selected rows. should be a list of strings that
* correspond to the 'matcher' property in selectableRows.
* - onSelect: expression invoked when a user makes a new selection. invoked
* with the following local variables:
* * columns: list of IDs of new selected columns, if any
* * rows: list of matchers of new selected rows, if any
*
* Usage:
* <piwik-series-picker />
*/
(function () {
angular.module('piwikApp').component('piwikSeriesPicker', {
templateUrl: 'plugins/CoreVisualizations/angularjs/series-picker/series-picker.component.html?cb=' + piwik.cacheBuster,
bindings: {
multiselect: '<',
selectableColumns: '<',
selectableRows: '<',
selectedColumns: '<',
selectedRows: '<',
onSelect: '&'
},
controller: SeriesPickerController
});
SeriesPickerController.$inject = [];
function SeriesPickerController() {
var vm = this;
vm.isPopupVisible = false;
// note: column & row states are separated since it's technically possible (though
// highly improbable) that a row value matcher will be the same as a recognized column.
vm.columnStates = {};
vm.rowStates = {};
vm.optionSelected = optionSelected;
vm.onLeavePopup = onLeavePopup;
vm.$onInit = $onInit;
function $onInit() {
vm.columnStates = getInitialOptionStates(vm.selectableColumns, vm.selectedColumns);
vm.rowStates = getInitialOptionStates(vm.selectableRows, vm.selectedRows);
}
function getInitialOptionStates(allOptions, selectedOptions) {
var states = {};
allOptions.forEach(function (columnConfig) {
states[columnConfig.column || columnConfig.matcher] = false;
});
selectedOptions.forEach(function (column) {
states[column] = true;
});
return states;
}
function optionSelected(optionValue, optionStates) {
if (!vm.multiselect) {
unselectOptions(vm.columnStates);
unselectOptions(vm.rowStates);
}
optionStates[optionValue] = !optionStates[optionValue];
if (optionStates[optionValue]) {
triggerOnSelectAndClose();
}
}
function onLeavePopup() {
vm.isPopupVisible = false;
if (optionsChanged()) {
triggerOnSelectAndClose();
}
}
function triggerOnSelectAndClose() {
if (!vm.onSelect) {
return;
}
vm.isPopupVisible = false;
vm.onSelect({
columns: getSelected(vm.columnStates),
rows: getSelected(vm.rowStates)
});
}
function optionsChanged() {
return !arrayEqual(getSelected(vm.columnStates), vm.selectedColumns)
|| !arrayEqual(getSelected(vm.rowStates), vm.selectedRows);
}
function arrayEqual(lhs, rhs) {
if (lhs.length !== rhs.length) {
return false;
}
return lhs
.filter(function (element) { return rhs.indexOf(element) === -1; })
.length === 0;
}
function unselectOptions(optionStates) {
Object.keys(optionStates).forEach(function (optionName) {
optionStates[optionName] = false;
});
}
function getSelected(optionStates) {
return Object.keys(optionStates).filter(function (optionName) {
return !! optionStates[optionName];
});
}
}
})();

View File

@ -0,0 +1,28 @@
piwik-series-picker {
display: inline-block;
.jqplot-seriespicker {
&:not(.open) {
opacity: .55;
}
&.open { // while open, make sure we're above other series picker icons
z-index: 1000;
}
> a {
display: inline-block;
opacity: 0;
position: absolute;
}
position: relative;
}
.jqplot-seriespicker-popover {
position: absolute;
top: -3px;
left: -4px;
}
}

View File

@ -0,0 +1,20 @@
<div class="singleMetricView" ng-class="{'loading': $ctrl.isLoading}">
<piwik-sparkline
class="metric-sparkline"
params="$ctrl.sparklineParams"
>
</piwik-sparkline>
<div class="metric-value">
<span title="{{ $ctrl.metricDocumentation }}">
<strong>{{ $ctrl.metricValue }}</strong> {{ ($ctrl.metricTranslation || '').toLowerCase() }}
</span>
<span class="metricEvolution"
ng-if="$ctrl.pastValue !== null"
title="{{ 'General_EvolutionSummaryGeneric'|translate:$ctrl.metricValue:$ctrl.getCurrentPeriod():$ctrl.pastValue:$ctrl.pastPeriod:$ctrl.metricChangePercent }}"
>
<span ng-class="{'positive-evolution': $ctrl.metricValueUnformatted > $ctrl.pastValueUnformatted, 'negative-evolution': $ctrl.metricValueUnformatted < $ctrl.pastValueUnformatted}">
{{ $ctrl.metricChangePercent }}
</span>
</span>
</div>
</div>

View File

@ -0,0 +1,279 @@
/*!
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <piwik-single-metric-view>
*/
(function () {
angular.module('piwikApp').component('piwikSingleMetricView', {
templateUrl: 'plugins/CoreVisualizations/angularjs/single-metric-view/single-metric-view.component.html?cb=' + piwik.cacheBuster,
bindings: {
metric: '<',
idGoal: '<',
metricTranslations: '<',
metricDocumentations: '<',
goals: '<',
goalMetrics: '<'
},
controller: SingleMetricViewController
});
SingleMetricViewController.$inject = ['piwik', 'piwikApi', '$element', '$httpParamSerializer', '$compile', '$scope', 'piwikPeriods', '$q'];
function SingleMetricViewController(piwik, piwikApi, $element, $httpParamSerializer, $compile, $scope, piwikPeriods, $q) {
var seriesPickerScope;
var vm = this;
vm.metricValue = null;
vm.isLoading = false;
vm.metricTranslation = null;
vm.metricDocumentation = null;
vm.selectableColumns = [];
vm.responses = null;
vm.sparklineParams = {};
vm.$onInit = $onInit;
vm.$onChanges = $onChanges;
vm.$onDestroy = $onDestroy;
vm.getCurrentPeriod = getCurrentPeriod;
vm.getMetricTranslation = getMetricTranslation;
vm.setMetric = setMetric;
function setSparklineParams() {
var params = { module: 'API', action: 'get', columns: vm.metric };
if (isIdGoalSet()) {
params.idGoal = vm.idGoal;
params.module = 'Goals';
}
vm.sparklineParams = params;
}
function $onInit() {
vm.selectedColumns = [vm.metric];
if (piwik.period !== 'range') {
vm.pastPeriod = getPastPeriodStr();
}
setSelectableColumns();
createSeriesPicker();
$element.closest('.widgetContent')
.on('widget:destroy', function() { $scope.$parent.$destroy(); })
.on('widget:reload', function() { $scope.$parent.$destroy(); });
setSparklineParams();
}
function $onChanges(changes) {
if (changes.metric && changes.metric.previousValue !== changes.metric.currentValue) {
onMetricChanged();
}
}
function $onDestroy() {
$element.closest('.widgetContent').off('widget:destroy').off('widget:reload');
destroySeriesPicker();
}
function fetchData() {
vm.isLoading = true;
var promises = [];
var apiModule = 'API';
var apiAction = 'get';
var extraParams = {};
if (isIdGoalSet()) {
extraParams.idGoal = vm.idGoal;
// the conversion rate added by the AddColumnsProcessedMetrics filter conflicts w/ the goals one, so don't run it
extraParams.filter_add_columns_when_show_all_columns = 0;
apiModule = 'Goals';
apiAction = 'get';
}
// first request for formatted data
promises.push(piwikApi.fetch($.extend({
method: apiModule + '.' + apiAction,
format_metrics: 'all'
}, extraParams)));
if (piwik.period !== 'range') {
// second request for unformatted data so we can calculate evolution
promises.push(piwikApi.fetch($.extend({
method: apiModule + '.' + apiAction,
format_metrics: '0'
}, extraParams)));
// third request for past data (unformatted)
promises.push(piwikApi.fetch($.extend({
method: apiModule + '.' + apiAction,
date: getLastPeriodDate(),
format_metrics: '0',
}, extraParams)));
// fourth request for past data (formatted for tooltip display)
promises.push(piwikApi.fetch($.extend({
method: apiModule + '.' + apiAction,
date: getLastPeriodDate(),
format_metrics: 'all',
}, extraParams)));
}
return $q.all(promises).then(function (responses) {
vm.responses = responses;
vm.isLoading = false;
});
}
function recalculateValues() {
// update display based on processed report metadata
setWidgetTitle();
vm.metricDocumentation = getMetricDocumentation();
// update data
var currentData = vm.responses[0];
vm.metricValue = currentData[vm.metric] || 0;
if (vm.responses[1]) {
vm.metricValueUnformatted = vm.responses[1][vm.metric];
var pastData = vm.responses[2];
vm.pastValueUnformatted = pastData[vm.metric] || 0;
var evolution = piwik.helper.calculateEvolution(vm.metricValueUnformatted, vm.pastValueUnformatted);
vm.metricChangePercent = (evolution * 100).toFixed(2) + ' %';
var pastDataFormatted = vm.responses[3];
vm.pastValue = pastDataFormatted[vm.metric] || 0;
} else {
vm.pastValue = null;
vm.metricChangePercent = null;
}
// don't change the metric translation until data is fetched to avoid loading state confusion
vm.metricTranslation = getMetricTranslation();
}
function getLastPeriodDate() {
var RangePeriod = piwikPeriods.get('range');
var result = RangePeriod.getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
return $.datepicker.formatDate('yy-mm-dd', result);
}
function setWidgetTitle() {
var title = vm.getMetricTranslation();
if (isIdGoalSet()) {
var goalName = vm.goals[vm.idGoal].name;
title = goalName + ' - ' + title;
}
$element.closest('div.widget').find('.widgetTop > .widgetName > span').text(title);
}
function getCurrentPeriod() {
if (piwik.startDateString === piwik.endDateString) {
return piwik.endDateString;
}
return piwik.startDateString + ', ' + piwik.endDateString;
}
function createSeriesPicker() {
vm.selectedColumns = [vm.idGoal ? ('goal' + vm.idGoal + '_' + vm.metric) : vm.metric];
var $widgetName = $element.closest('div.widget').find('.widgetTop > .widgetName');
var $seriesPicker = $('<piwik-series-picker class="single-metric-view-picker" multiselect="false" ' +
'selectable-columns="$ctrl.selectableColumns" selectable-rows="[]" selected-columns="$ctrl.selectedColumns" ' +
'selected-rows="[]" on-select="$ctrl.setMetric(columns[0])" />');
seriesPickerScope = $scope.$new();
$compile($seriesPicker)(seriesPickerScope);
$widgetName.append($seriesPicker);
}
function destroySeriesPicker() {
$element.closest('div.widget').find('.single-metric-view-picker').remove();
seriesPickerScope.$destroy();
seriesPickerScope = null;
}
function getMetricDocumentation() {
if (!vm.metricDocumentations || !vm.metricDocumentations[vm.metric]) {
return '';
}
return vm.metricDocumentations[vm.metric];
}
function getMetricTranslation() {
if (!vm.metricTranslations || !vm.metricTranslations[vm.metric]) {
return '';
}
return vm.metricTranslations[vm.metric];
}
function setSelectableColumns() {
var result = [];
Object.keys(vm.metricTranslations).forEach(function (column) {
result.push({ column: column, translation: vm.metricTranslations[column] });
});
Object.keys(vm.goals).forEach(function (idgoal) {
var goal = vm.goals[idgoal];
vm.goalMetrics.forEach(function (column) {
result.push({
column: 'goal' + goal.idgoal + '_' + column,
translation: goal.name + ' - ' + vm.metricTranslations[column]
});
});
});
vm.selectableColumns = result;
}
function onMetricChanged() {
setSparklineParams();
fetchData().then(recalculateValues);
// notify widget of parameter change so it is replaced
$element.closest('[widgetId]').trigger('setParameters', { column: vm.metric, idGoal: vm.idGoal });
}
function setMetric(newColumn) {
var idGoal;
var m = newColumn.match(/^goal([0-9]+)_(.*)/);
if (m) {
idGoal = +m[1];
newColumn = m[2];
}
if (vm.metric !== newColumn || idGoal !== vm.idGoal) {
vm.metric = newColumn;
vm.idGoal = idGoal;
onMetricChanged();
}
}
function getPastPeriodStr() {
var startDate = piwikPeriods.get('range').getLastNRange(piwik.period, 2, piwik.currentDateString).startDate;
var dateRange = piwikPeriods.get(piwik.period).parse(startDate).getDateRange();
return $.datepicker.formatDate('yy-mm-dd', dateRange[0]) + ',' + $.datepicker.formatDate('yy-mm-dd', dateRange[1]);
}
function isIdGoalSet() {
return vm.idGoal || vm.idGoal === 0;
}
}
})();

View File

@ -0,0 +1,61 @@
.singleMetricView {
margin: 5px 12px 10px;
display: inline-block;
&.loading {
opacity: 0.5;
}
.metric-value {
display: inline-block;
font-size: 14px;
line-height: 25px;
vertical-align: top;
margin-left: 3px;
}
.metric-sparkline {
display: inline-block;
vertical-align: top;
img {
width: 100px;
height: 25px;
}
}
.metricEvolution {
font-weight: bold;
font-size: 12px;
> span {
display: inline-block;
&:not(.positive-evolution):not(.negative-evolution) {
margin-left: 8px;
}
}
.positive-evolution::before {
content: "";
background: url(plugins/MultiSites/images/arrow_up.png) no-repeat center center;
display: inline-block;
height: 7px;
width: 12px;
}
.negative-evolution::before {
content: "";
background: url(plugins/MultiSites/images/arrow_down.png) no-repeat center center;
display: inline-block;
height: 7px;
width: 12px;
}
}
}
[piwik-single-metric-view] {
display: block;
}
.single-metric-view-picker {
margin-left: 6px;
}