Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
585
assets/webapps/costcontrol.gaiamobile.org/js/utils/chart.js
Normal file
585
assets/webapps/costcontrol.gaiamobile.org/js/utils/chart.js
Normal file
|
|
@ -0,0 +1,585 @@
|
|||
/* global _, debug, Toolkit, Formatting, Common */
|
||||
/* exported ChartUtils */
|
||||
'use strict';
|
||||
|
||||
var ChartUtils = (function() {
|
||||
var DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
|
||||
var DAY = 24 * 60 * 60 * 1000;
|
||||
var NEVER_PERIOD = 30 * DAY;
|
||||
var NEVER_ANCHOR = 21 * DAY;
|
||||
var CHART_BG_RATIO = 0.87;
|
||||
var FONT_SIZE = toDevicePixels(13);
|
||||
var FONT_WEIGHT = '600';
|
||||
var FONT_WEIGHT_AXIS = '400';
|
||||
var WIFI_CHART_STROKE = '#8b9052';
|
||||
var WIFI_CHART_FILL = '#cbd936';
|
||||
var MOBILE_CHART_STROKE = '#762d4a';
|
||||
var MOBILE_CHART_FILL = 'rgba(147, 21, 98, 0.7)';
|
||||
|
||||
function toDevicePixels(value) {
|
||||
return value * DEVICE_PIXEL_RATIO;
|
||||
}
|
||||
|
||||
function makeCSSFontString(fontSize, fontWeight) {
|
||||
return fontWeight + ' ' + fontSize + 'px sans-serif';
|
||||
}
|
||||
|
||||
function formatChartDate(date) {
|
||||
return _('verbose-chart-date-format', {
|
||||
'monthday-number': date.getDate(),
|
||||
'em-month': _('month-' + date.getMonth() + '-short')
|
||||
});
|
||||
}
|
||||
|
||||
function getLimitInBytes(settings) {
|
||||
var multiplier = 1000000; // MB
|
||||
if (settings.dataLimitUnit === 'GB') {
|
||||
multiplier = 1000000000; // GB
|
||||
}
|
||||
|
||||
var value = settings.dataLimitValue;
|
||||
return (value && value !== 0) ? value * multiplier : null;
|
||||
}
|
||||
|
||||
function calculateUpperDate(settings) {
|
||||
var today = new Date();
|
||||
var trackingPeriod = settings.trackingPeriod;
|
||||
var nextReset = settings.nextReset;
|
||||
if (trackingPeriod !== 'never' && nextReset) {
|
||||
return new Date(nextReset.getTime() - DAY);
|
||||
}
|
||||
|
||||
var lastReset = settings.lastCompleteDataReset || today;
|
||||
var offset = today.getTime() - lastReset.getTime();
|
||||
var upperDate = new Date(lastReset.getTime() + NEVER_PERIOD);
|
||||
if (offset >= NEVER_ANCHOR) {
|
||||
upperDate = new Date(today.getTime() + (NEVER_PERIOD - NEVER_ANCHOR));
|
||||
}
|
||||
|
||||
debug('Upper date:', upperDate);
|
||||
return upperDate;
|
||||
}
|
||||
|
||||
function calculateLowerDate(settings) {
|
||||
var today = new Date();
|
||||
var lowerDate = Toolkit.toMidnight(new Date());
|
||||
var nextReset = settings.nextReset || lowerDate;
|
||||
var trackingPeriod = settings.trackingPeriod;
|
||||
|
||||
if (trackingPeriod === 'weekly') {
|
||||
lowerDate.setTime(nextReset.getTime() - (7 * DAY));
|
||||
} else if (trackingPeriod === 'monthly') {
|
||||
var monthDate = settings.resetTime;
|
||||
var isFirstDayOfPeriod = today.getDate() == monthDate;
|
||||
var isAfterFirstDayOfPeriod = today.getDate() > monthDate;
|
||||
if (isAfterFirstDayOfPeriod) {
|
||||
// lowerDate is in the current month
|
||||
lowerDate.setDate(monthDate);
|
||||
} else if (!isFirstDayOfPeriod) {
|
||||
// lowerDate is on the previous month
|
||||
var LAST_DAY_OF_PREVIOUS_MONTH = 0;
|
||||
var newMonth = today.getMonth() - 1;
|
||||
var newYear = today.getFullYear();
|
||||
if (newMonth < 0) {
|
||||
newMonth = 11;
|
||||
newYear--;
|
||||
}
|
||||
|
||||
lowerDate = Toolkit.toMidnight(new Date(newYear, newMonth, monthDate));
|
||||
// The day of the month of lowerDate is different to settings.resetTime
|
||||
// value when the resetTime day doesn't exists this month, (eg. 30th
|
||||
// February), on this case, the lowerDate must be the last day of the
|
||||
// previous month
|
||||
if (lowerDate.getDate() != monthDate) {
|
||||
lowerDate = Toolkit.toMidnight(new Date());
|
||||
lowerDate.setDate(LAST_DAY_OF_PREVIOUS_MONTH);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var lastReset = settings.lastCompleteDataReset || lowerDate;
|
||||
lowerDate = lastReset;
|
||||
var offset = today.getTime() - lastReset.getTime();
|
||||
if (offset >= NEVER_ANCHOR) {
|
||||
lowerDate = new Date(today.getTime() - NEVER_ANCHOR);
|
||||
}
|
||||
}
|
||||
|
||||
debug('Lower date:', lowerDate);
|
||||
return lowerDate;
|
||||
}
|
||||
|
||||
// Expand the model with some computed values
|
||||
function expandModel(base) {
|
||||
|
||||
// Update today
|
||||
var today = Toolkit.toMidnight(new Date());
|
||||
|
||||
// Graphic settings
|
||||
base.originY = Math.floor(base.height * CHART_BG_RATIO);
|
||||
|
||||
// Today value
|
||||
base.axis.X.today = today;
|
||||
|
||||
// Normalize today
|
||||
Toolkit.toMidnight(base.axis.X.today);
|
||||
Toolkit.toMidnight(base.axis.X.lower);
|
||||
Toolkit.toMidnight(base.axis.X.upper);
|
||||
|
||||
// X axis projection function to convert a value into a pixel value
|
||||
var xLowerBound = base.axis.X.lower.getTime();
|
||||
var xSize = base.axis.X.upper.getTime() - xLowerBound;
|
||||
var realWidth = base.endX - base.originX;
|
||||
base.axis.X.len = realWidth;
|
||||
base.axis.X.get = function cc_dataToXPx(value) {
|
||||
var projection = (value.getTime() - xLowerBound) / xSize;
|
||||
return projection * realWidth + base.originX;
|
||||
};
|
||||
|
||||
// Y max value
|
||||
var limitEnabled = true; // XXX: model.limits.enabled;
|
||||
base.axis.Y.maxValue = Math.max(limitEnabled ? base.limits.value : 0,
|
||||
base.data.mobile.total,
|
||||
base.data.wifi.total);
|
||||
|
||||
// Y axis projection function and automatic values
|
||||
base.axis.Y.range = base.axis.Y.upper - base.axis.Y.lower;
|
||||
base.axis.Y.upper = (1 + base.axis.Y.margin) * base.axis.Y.maxValue;
|
||||
var yLowerBound = base.axis.Y.lower;
|
||||
var ySize = base.axis.Y.upper - yLowerBound;
|
||||
base.axis.Y.get = function cc_dataToYPx(value) {
|
||||
var projection = (value - yLowerBound) / ySize;
|
||||
return base.originY * (1 - projection);
|
||||
};
|
||||
base.axis.Y.step = base.axis.Y.get(base.axis.Y.maxValue);
|
||||
|
||||
// Limits
|
||||
base.limits.warning = Common.DATA_USAGE_WARNING;
|
||||
base.limits.warningValue = base.limits.value * Common.DATA_USAGE_WARNING;
|
||||
}
|
||||
|
||||
// Check if the segment of the graph is inside chart area. If so, draw it
|
||||
function clipAndDrawSegment(ctx, model, x0, y0, x1, y1) {
|
||||
if (x0 >= model.originX && x1 <= model.endX) {
|
||||
var x0Fixed = Math.floor(x0) - 0.5;
|
||||
var x1Fixed = Math.floor(x1) + 0.5;
|
||||
|
||||
// Fill
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
ctx.beginPath();
|
||||
ctx.lineTo(x1Fixed, y1);
|
||||
ctx.lineTo(x1Fixed, model.originY);
|
||||
ctx.lineTo(x0Fixed, model.originY);
|
||||
ctx.lineTo(x0Fixed, y0);
|
||||
ctx.moveTo(x1Fixed, y0);
|
||||
ctx.fill();
|
||||
|
||||
// Stroke
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.beginPath();
|
||||
// It's necessary add 1 to the X coordinate to make up for the offset
|
||||
// produced with the drawing of the previous line.
|
||||
ctx.moveTo(x0Fixed + 1, y0);
|
||||
ctx.lineTo(x1Fixed, y1);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawTodayMark(ctx, x, y, color) {
|
||||
ctx.save();
|
||||
var radius = toDevicePixels(4);
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawBackgroundLayer(canvas, model, showMobile) {
|
||||
canvas.height = model.height;
|
||||
var width = canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.save();
|
||||
|
||||
// White bg
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, width, model.originY);
|
||||
|
||||
// Horizontal lines every step
|
||||
var step = model.axis.Y.step;
|
||||
var limitY = model.axis.Y.get(model.limits.value);
|
||||
ctx.strokeStyle = '#e0e0e0';
|
||||
ctx.lineWidth = toDevicePixels(1);
|
||||
var displayLimit = model.limits.enabled && showMobile;
|
||||
for (var y = model.originY - step; y > step; y -= step) {
|
||||
if (displayLimit && same(y, limitY, 0.1)) {
|
||||
continue;
|
||||
}
|
||||
var drawY = Math.floor(y) - 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(model.originX, drawY);
|
||||
ctx.lineTo(model.endX, drawY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Vertical lines every day
|
||||
var days = (model.axis.X.upper - model.axis.X.lower) / DAY;
|
||||
step = model.axis.X.len / days;
|
||||
ctx.strokeStyle = '#eeeeee';
|
||||
ctx.lineWidth = toDevicePixels(1);
|
||||
for (var x = model.originX; x <= model.endX; x += step) {
|
||||
var drawX = Math.floor(x) + 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(drawX, model.originY);
|
||||
ctx.lineTo(drawX, 0);
|
||||
ctx.stroke();
|
||||
// Ensure draw the last vertical line
|
||||
if (((x + step) > model.endX) && (x !== model.endX)) {
|
||||
// the 0.5 offset is needed to avoid drawing a double line.
|
||||
drawX = model.endX - 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(drawX, model.originY);
|
||||
ctx.lineTo(drawX, 0);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// Return true if a and b not differ more than threshold
|
||||
function same(a, b, threshold) {
|
||||
threshold = threshold || 0;
|
||||
return Math.abs(a - b) <= threshold;
|
||||
}
|
||||
}
|
||||
|
||||
function drawTodayLayer(canvas, model) {
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Compute the X offset
|
||||
var offsetX = model.axis.X.get(model.axis.X.today);
|
||||
|
||||
// Configure Centered today text
|
||||
var marginTop = toDevicePixels(10);
|
||||
|
||||
var todayTag = formatChartDate(model.axis.X.today);
|
||||
|
||||
// Render the text
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT);
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
// Update measues of today label
|
||||
var metrics = ctx.measureText(todayTag);
|
||||
model.todayLabel.x0 = offsetX - metrics.width / 2;
|
||||
model.todayLabel.x1 = offsetX + metrics.width / 2;
|
||||
if (model.todayLabel.x1 > model.endX) {
|
||||
ctx.textAlign = 'right';
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(todayTag, offsetX, model.originY + marginTop);
|
||||
|
||||
setAccessibilityAttributes(canvas, 'today-layer', { today: todayTag });
|
||||
}
|
||||
|
||||
function drawAxisLayer(canvas, model, showMobile) {
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Start drawing Y axis
|
||||
var step = model.axis.Y.step;
|
||||
var dataStep = model.axis.Y.upper - model.axis.Y.maxValue;
|
||||
var marginRight = 4;
|
||||
var offsetX = model.originX - marginRight;
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT_AXIS);
|
||||
ctx.textAlign = 'right';
|
||||
var displayLimit = showMobile && model.limits.enabled;
|
||||
var lastUnit;
|
||||
for (var y = 0.5 + model.originY, value = 0;
|
||||
y > step; y -= step, value += dataStep) {
|
||||
|
||||
// First X label for 0 is aligned with the bottom
|
||||
if (value === 0) {
|
||||
lastUnit = Formatting.smartRound(dataStep, 0)[1];
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillStyle = '#6a6a6a';
|
||||
ctx.fillText(Formatting.formatData([0, lastUnit]), offsetX, y - 2.5);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rest of labels are aligned with the middle
|
||||
var rounded = Formatting.smartRound(value, -1);
|
||||
var v = rounded[0];
|
||||
var u = rounded[1];
|
||||
var label = v;
|
||||
if (lastUnit !== u) {
|
||||
label = Formatting.formatData([v, u]);
|
||||
}
|
||||
|
||||
lastUnit = u;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = displayLimit &&
|
||||
(value === model.limits.value) &&
|
||||
model.limits.enabled ?
|
||||
'#b50202' : '#6a6a6a';
|
||||
ctx.fillText(label, offsetX, y);
|
||||
}
|
||||
|
||||
// Now the X axis
|
||||
ctx.fillStyle = '#6a6a6a';
|
||||
var marginTop = toDevicePixels(10);
|
||||
|
||||
// Left tag
|
||||
var leftTag = formatChartDate(model.axis.X.lower);
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT);
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'left';
|
||||
|
||||
var isBelowToday = model.todayLabel.x0 <=
|
||||
model.originX + ctx.measureText(leftTag).width;
|
||||
if (!isBelowToday) {
|
||||
ctx.fillText(leftTag, model.originX, model.originY + marginTop);
|
||||
}
|
||||
|
||||
// Right tag
|
||||
var rightTag = formatChartDate(model.axis.X.upper);
|
||||
ctx.textAlign = 'right';
|
||||
|
||||
isBelowToday = model.todayLabel.x1 >=
|
||||
model.endX - ctx.measureText(rightTag).width;
|
||||
if (!isBelowToday) {
|
||||
ctx.fillText(rightTag, model.endX, model.originY + marginTop);
|
||||
}
|
||||
|
||||
setAccessibilityAttributes(canvas, 'axis-layer',
|
||||
{ from: leftTag, to: rightTag });
|
||||
}
|
||||
|
||||
function setAccessibilityAttributes(element, identifier, data) {
|
||||
if (identifier) {
|
||||
element.setAttribute('data-l10n-id', identifier);
|
||||
if (data) {
|
||||
element.setAttribute('data-l10n-args', JSON.stringify(data));
|
||||
}
|
||||
} else {
|
||||
element.removeAttribute('data-l10n-id');
|
||||
element.removeAttribute('data-l10n-args');
|
||||
element.removeAttribute('aria-label');
|
||||
}
|
||||
}
|
||||
|
||||
function drawLimits(canvas, model, showMobile) {
|
||||
var set = model.limits.value;
|
||||
var color = '#b50202';
|
||||
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var displayLimit = showMobile && model.limits.enabled;
|
||||
if (!displayLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
|
||||
var marginTop = 1;
|
||||
var offsetY = set ? model.axis.Y.get(model.limits.value) :
|
||||
FONT_SIZE + 2 * marginTop;
|
||||
|
||||
ctx.font = makeCSSFontString(FONT_SIZE, FONT_WEIGHT);
|
||||
|
||||
// The dashed limit line
|
||||
var lineLength = toDevicePixels(15);
|
||||
var gapLength = toDevicePixels(7);
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = toDevicePixels(1);
|
||||
ctx.beginPath();
|
||||
for (var x = model.originX, drawY = Math.floor(offsetY) - 0.5;
|
||||
x < model.endX; x += gapLength) {
|
||||
ctx.moveTo(x, drawY);
|
||||
ctx.lineTo(Math.min(x += lineLength, model.endX), drawY);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
setAccessibilityAttributes(canvas,
|
||||
'limit-layer-' + model.limits.dataLimitUnit,
|
||||
{ value: model.limits.dataLimitValue });
|
||||
}
|
||||
|
||||
function drawDataLayer(canvas, model, sampleType, style) {
|
||||
var samples = (model.data[sampleType] || {}).samples;
|
||||
if (!samples) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// Style
|
||||
ctx.fillStyle = style.fill;
|
||||
ctx.strokeStyle = style.stroke;
|
||||
ctx.lineWidth = toDevicePixels(2);
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.moveTo(model.originX, model.originY);
|
||||
|
||||
var today = Toolkit.toMidnight(new Date());
|
||||
// offset in milliseconds
|
||||
var offset = today.getTimezoneOffset() * 60 * 1000;
|
||||
|
||||
var sum = 0; var x, y = model.originY;
|
||||
var lastX = model.originX, lastY = model.axis.Y.get(sum);
|
||||
// Only dealing with the use cases of negative UTC offset because, due to
|
||||
// the database granularity, the returned samples are given in UTC days, and
|
||||
// we don't know what part of the traffic sample values correspond to the
|
||||
// current localtime day. The query for the samples is generated on the
|
||||
// costcontrol module, and the case into which the returned samples only
|
||||
// have one record with the data for the localtime day of yesterday is not
|
||||
// possible.
|
||||
|
||||
for (var i = 0, len = samples.length; i < len; i++) {
|
||||
var sample = samples[i];
|
||||
var sampleLocalTime = sample.date.getTime() + offset;
|
||||
var sampleUTCDate = Toolkit.toMidnight(new Date(sampleLocalTime));
|
||||
|
||||
var isToday = (today.getTime() === sampleUTCDate.getTime());
|
||||
var isTomorrow = (today.getTime() + DAY === sampleUTCDate.getTime());
|
||||
var thereIsATomorrowSample = (isToday && (i + 2 === len));
|
||||
// Depends on the hour of the day and the offset, it is possible the
|
||||
// networkStats API returns the current data mobile in the tomorrow
|
||||
// sample, because on the UTC hour is another day.
|
||||
if (thereIsATomorrowSample) {
|
||||
// Join the value of the samples for today and tomorrow
|
||||
var tomorrowSample = samples[i+1];
|
||||
if (typeof sample.value === 'undefined') {
|
||||
sample.value = tomorrowSample.value;
|
||||
} else if (typeof tomorrowSample.value !== 'undefined') {
|
||||
sample.value += tomorrowSample.value;
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
lastX = model.axis.X.get(sample.date);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (typeof sample.value === 'undefined') {
|
||||
lastX = x = model.axis.X.get(sample.date);
|
||||
ctx.moveTo(x, y);
|
||||
|
||||
} else {
|
||||
if (i === 0) {
|
||||
lastX = model.axis.X.get(sample.date);
|
||||
}
|
||||
|
||||
sum += sample.value;
|
||||
x = model.axis.X.get(sample.date);
|
||||
y = model.axis.Y.get(sum);
|
||||
|
||||
clipAndDrawSegment(ctx, model, lastX, lastY, x, y);
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
var onlyExistTomorrowSample = (i===0 && isTomorrow);
|
||||
var isXInsideTheGraph = (x >= model.originX);
|
||||
if ((isToday || onlyExistTomorrowSample) && isXInsideTheGraph) {
|
||||
drawTodayMark(ctx, x, y, style.stroke);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!style.pattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pattern = ctx.createPattern(style.pattern, 'repeat');
|
||||
ctx.globalCompositeOperation = 'source-atop';
|
||||
ctx.fillStyle = pattern;
|
||||
ctx.fillRect(0, 0, model.width, model.originY);
|
||||
}
|
||||
|
||||
function drawWarningLayer(canvas, model) {
|
||||
canvas.height = model.height;
|
||||
canvas.width = model.width;
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
if (!model.limits.enabled || model.limits.value === null) {
|
||||
setAccessibilityAttributes(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
// No problem here
|
||||
var mobileUsage = model.data.mobile.total;
|
||||
var mobileUsageDifference = mobileUsage - model.limits.value;
|
||||
if (mobileUsage <= model.limits.warningValue) {
|
||||
setAccessibilityAttributes(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
// Warning mode
|
||||
if (mobileUsage <= model.limits.value) {
|
||||
var limitValue = Math.floor(model.axis.Y.get(model.limits.value));
|
||||
var warningValue =
|
||||
Math.round(model.axis.Y.get(model.limits.warningValue));
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'rgba(255, 112, 0, 0.5)';
|
||||
ctx.fillRect(
|
||||
model.originX, limitValue,
|
||||
model.axis.X.len + 0.5, warningValue - limitValue
|
||||
);
|
||||
|
||||
setAccessibilityAttributes(canvas, 'warning-layer', {
|
||||
value: Formatting.formatData(Formatting.roundData(
|
||||
-mobileUsageDifference))
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit exceeded
|
||||
var limitValueExceeded = model.axis.Y.get(model.limits.value);
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
|
||||
ctx.fillRect(
|
||||
model.originX, 0,
|
||||
model.axis.X.len + 0.5, limitValueExceeded
|
||||
);
|
||||
setAccessibilityAttributes(canvas, 'limit-exceeded-layer', {
|
||||
value: Formatting.formatData(Formatting.roundData(mobileUsageDifference))
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// Constants
|
||||
WIFI_CHART_STROKE: WIFI_CHART_STROKE,
|
||||
WIFI_CHART_FILL: WIFI_CHART_FILL,
|
||||
MOBILE_CHART_STROKE: MOBILE_CHART_STROKE,
|
||||
MOBILE_CHART_FILL: MOBILE_CHART_FILL,
|
||||
|
||||
// Functions
|
||||
toDevicePixels: toDevicePixels,
|
||||
makeCSSFontString: makeCSSFontString,
|
||||
formatChartDate: formatChartDate,
|
||||
getLimitInBytes: getLimitInBytes,
|
||||
calculateUpperDate: calculateUpperDate,
|
||||
calculateLowerDate: calculateLowerDate,
|
||||
expandModel: expandModel,
|
||||
clipAndDrawSegment: clipAndDrawSegment,
|
||||
drawTodayMark: drawTodayMark,
|
||||
drawBackgroundLayer: drawBackgroundLayer,
|
||||
drawTodayLayer: drawTodayLayer,
|
||||
drawAxisLayer: drawAxisLayer,
|
||||
drawLimits: drawLimits,
|
||||
drawDataLayer: drawDataLayer,
|
||||
drawWarningLayer: drawWarningLayer
|
||||
};
|
||||
}());
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
;'use strict';var DEBUGGING=false;var debug=(function(){var SEQ_ID=0;var PROCESS_ID=Date.now();var DEBUG_PREFIX='CC';return function _debug(){if(!DEBUGGING){return;}
|
||||
var currentWindow=window;var parents=[];while(currentWindow!==currentWindow.parent){parents.push(currentWindow.location.pathname);currentWindow=currentWindow.parent;}
|
||||
parents.push(currentWindow.location.pathname);parents=parents.reverse().join('>')+':';var uId=PROCESS_ID;var message=['('+uId+'-'+(SEQ_ID++)+')',DEBUG_PREFIX,parents];for(var i=0,len=arguments.length,obj;i<len;i++){obj=arguments[i];message.push(typeof obj==='object'?JSON.stringify(obj):obj);}
|
||||
if(window.dump){message.push('\n');window.dump(message.join(' '));}else if(console&&console.log){console.log(message.join(' '));}};}());
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
;'use strict';var Formatting=(function(){var MINUTE=60*1000;var HOUR=60*MINUTE;var DAY=24*HOUR;function getDaysOfDifference(a,b){return Math.floor((a.getTime()-b.getTime())/DAY);}
|
||||
function getFormattedDate(timestamp,formatter){return formatter.format(timestamp);}
|
||||
function formatTime(timestamp,formatter){if(!timestamp){return _('never');}
|
||||
var now=new Date(),then=new Date(timestamp);if(formatter){return getFormattedDate(then,formatter);}
|
||||
var date,time;var daysOfDifference=getDaysOfDifference(now,then);if(daysOfDifference===0){date=_('today');}else if(daysOfDifference===1){date=_('yesterday');}else{date=getFormattedDate(timestamp,Formatting.formatters.shortWeekday);}
|
||||
time=getFormattedDate(timestamp,Formatting.formatters.shortTime);return _('day-hour-format',{day:date,time:time});}
|
||||
function formatTimeSinceNow(timestamp){var now=new Date(),then=new Date(timestamp);var formattedTime=formatTime(timestamp);if(getDaysOfDifference(now,then)>0){return formattedTime;}
|
||||
var age=now-then;if(age<MINUTE){formattedTime=_('minutes-ago-short',{value:0});}else if(age<HOUR){var minutes=Math.floor(age/MINUTE);formattedTime=_('minutes-ago-short',{value:minutes});}else if(age<DAY){var hours=Math.floor(age/HOUR);formattedTime=_('hours-ago-short',{value:hours});}
|
||||
return formattedTime;}
|
||||
function formatData(dataArray){return isNaN(dataArray[0])?'':_('magnitude',{value:dataArray[0],unit:dataArray[1]});}
|
||||
function roundData(value,positions){positions=(typeof positions==='undefined')?2:positions;if(value<1000){return[value.toFixed(positions),_('B')];}
|
||||
if(value<1000000){return[(value/1000).toFixed(positions),_('KB')];}
|
||||
if(value<1000000000){return[(value/1000000).toFixed(positions),_('MB')];}
|
||||
return[(value/1000000000).toFixed(positions),_('GB')];}
|
||||
function getPositions(value,deltaPositions){deltaPositions=(typeof deltaPositions==='undefined')?0:deltaPositions;if(parseInt(value)===value){return 0;}
|
||||
if(value<10){return 2+deltaPositions;}
|
||||
if(value<100){return 1+deltaPositions;}
|
||||
return 0;}
|
||||
function smartRound(value,deltaPositions){deltaPositions=(typeof deltaPositions==='undefined')?0:deltaPositions;if(value<1000){var bPositions=getPositions(value,deltaPositions);return[value.toFixed(bPositions),_('B')];}
|
||||
if(value<1000000){var kbytes=value/1000;var kbPositions=getPositions(kbytes,deltaPositions);return[kbytes.toFixed(kbPositions),_('KB')];}
|
||||
if(value<1000000000){var mbytes=value/1000000;var mPositions=getPositions(mbytes,deltaPositions);return[mbytes.toFixed(mPositions),_('MB')];}
|
||||
var gbytes=value/1000000000;var gPositions=getPositions(gbytes,deltaPositions);return[gbytes.toFixed(gPositions),_('GB')];}
|
||||
function formatTimeHTML(timestampA,timestampB){function timeElement(content){var time=document.createElement('time');time.textContent=content;return time;}
|
||||
var fragment=document.createDocumentFragment();if(typeof timestampB==='undefined'){fragment.appendChild(timeElement(Formatting.formatTime(timestampA)));return fragment;}
|
||||
var dateA=new Date(timestampA);var dateB=new Date(timestampB);if(dateA.getFullYear()===dateB.getFullYear()&&dateA.getMonth()===dateB.getMonth()&&dateA.getDay()===dateB.getDay()){return formatTimeHTML(timestampB);}
|
||||
fragment.appendChild(timeElement(Formatting.formatTime(timestampA,Formatting.formatters.shortDate)));fragment.appendChild(document.createTextNode(' – '));fragment.appendChild(timeElement(Formatting.formatTime(timestampB)));return fragment;}
|
||||
function computeTelephonyMinutes(activity){return Math.ceil(activity.calltime/60000);}
|
||||
return{getFormattedDate:getFormattedDate,formatTime:formatTime,formatTimeSinceNow:formatTimeSinceNow,formatData:formatData,roundData:roundData,getPositions:getPositions,smartRound:smartRound,formatTimeHTML:formatTimeHTML,computeTelephonyMinutes:computeTelephonyMinutes,formatters:{}};}());function setFormatters(){const locales=navigator.languages;Formatting.formatters.longDate=Intl.DateTimeFormat(locales,{month:'long',day:'numeric',year:'numeric'});Formatting.formatters.shortDate=Intl.DateTimeFormat(locales,{month:'short',day:'numeric',});Formatting.formatters.shortTime=Intl.DateTimeFormat(locales,{hour12:navigator.mozHour12,hour:'numeric',minute:'numeric'});Formatting.formatters.shortWeekday=Intl.DateTimeFormat(locales,{weekday:'short'});}
|
||||
setFormatters();window.addEventListener('languagechange',setFormatters,false);window.addEventListener('timeformatchange',setFormatters,false);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
;'use strict';var _=function cc_fallbackTranslation(keystring){var r=window.navigator.mozL10n.get.apply(this,arguments);return r||(DEBUGGING?'!!':'')+keystring;};function deepCopy(object){if(object===null||!(object instanceof Object)){return object;}
|
||||
var clone,constructor=object.constructor;switch(constructor){case Date:clone=new constructor(object.getTime());break;default:clone=constructor();break;}
|
||||
for(var key in object){if(object.hasOwnProperty(key)){clone[key]=deepCopy(object[key]);}}
|
||||
return clone;}
|
||||
var Toolkit={checkEnoughDelay:function(threshold,dateA,dateB){if(!dateA){return true;}
|
||||
var end=dateB||new Date();return(end.getTime()-dateA.getTime())>threshold;},toMidnight:function _toMidnight(date){date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0);return date;}};
|
||||
Loading…
Reference in a new issue