Biểu đồ hạn ngạch Gmail

By kimkha Last update Apr 11, 2009 — Installed 23 times. Daily Installs: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

There are 1 previous version of this script.

scr_meta=<><![CDATA[
// ==UserScript==
// @name          Biểu đồ hạn ngạch Gmail
// @version       1.0
// @namespace     http://kakalia.co.cc
// @author        Nguyễn Kim Kha
// @description   Hiển thị biểu đồ sử dụng dung lượng và tổng dung lượng còn lại của tài khoản Gmail của bạn
// @include       http*://mail.google.com/*
// ==/UserScript==
]]></>;

function Graph(firstDate, style, serializedValues) {
    this.firstDate = firstDate;
    this.style = style;
    this.values = [];
    if (serializedValues) {
        var sValues = serializedValues.split(/,/);
        var lastValue = 0;
        for (var i = 0; i < sValues.length; i++) {
            if (sValues[i] == '-') {
                this.values.push(null);
            } else {
                var value = parseInt(sValues[i]);
                this.values.push(lastValue + value);
                lastValue += value;
            }
        }
    }
}

Graph.prototype = {
    getIndexFromDate: function(date) {
        var index = date.daysFrom(this.firstDate);
        if (index < 0) {
            index = 0;
        } else if (index >= this.values.length) {
            index = this.values.length - 1;
        } else {
            while (this.values[index] == null && index < this.values.length - 1) {
                index++;
            }
        }
        return index;
    },

    getValue: function(date, dateReceiver) {
        var index = this.getIndexFromDate(date);
        var value = this.values[index];
        if (dateReceiver) {
            dateReceiver(this.firstDate.addDate(index), value);
        }
        return value;
    },

    setValue: function(date, value) {
        var index = date.daysFrom(this.firstDate);
        if (index >= 0) {
            this.values[index] = value;
        }
    },

    drawable: function() {
        if (this.values.length > 1) {
            return this;
        } else {
            var values = this.values[0] + ",0";
            var graph = new Graph(this.firstDate.addDate(-1), this.style, values);
            graph.getIndexFromDate = function(date) { return 1; }
            return graph;
        }
    },

    serializeValues: function() {
        var ser = new Array(this.values.length);
        var lastValue = 0;
        for (var i = 0; i < ser.length; i++) {
            if (this.values[i] != null) {
                ser[i] = this.values[i] - lastValue;
                lastValue = this.values[i];
            } else {
                ser[i] = '-';
            }
        }
        return ser.join(",");
    },

    max: function() {
        var max = 1;
        for (var i = 0; i < this.values.length; i++) {
            if (this.values[i] != null) {
                max = Math.max(max, this.values[i]);
            }
        }
        return max;
    }
}

function SimpleDate(time) {
    this.time = time;
}

SimpleDate.parse = function(text) {
    return new SimpleDate(Date.parse(text));
}

SimpleDate.today = function() {
    var today = new Date();
    today.setHours(0, 0, 0, 0);
    return new SimpleDate(today.getTime());
}

SimpleDate.MONTHS = ["Tháng1", "Tháng2", "Tháng3", "Tháng4", "Tháng5", "Tháng6", "Tháng7", "Tháng8", "Tháng9", "Tháng10", "Tháng11", "Tháng12"];

SimpleDate.prototype = {
    toString: function() {
        var date = new Date(this.time);
        var format2 = function(n) {
            return n >= 10 ? ("" + n) : ("0" + n);
        };
        return date.getFullYear() + "/" + format2(date.getMonth() + 1) + "/" + format2(date.getDate());
    },

    displayDate: function() {
        var date = new Date(this.time);
        return SimpleDate.MONTHS[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
        // return (new Date(this.time)).toLocaleFormat("%b %e, %Y"); // toLocaleFormat is buggy in Windows
    },
    
    daysFrom: function(date) {
        return (this.time - date.time) / 86400000;
    },

    addDate: function(i) {
        return new SimpleDate(this.time + 86400000 * i);
    }
}

function Account(name) {
    this.prefix = name + "/";
}

Account.prototype = {
    getValue: function(key, defaultValue) {
        var val = GM_getValue(this.prefix + key);
        return val || defaultValue;
    },

    setValue: function(key, value) {
        GM_setValue(this.prefix + key, value);
    }
}

function Label(parent, style) {
    this.element = document.createElement("div");
    this.element.style.fontSize = "11px";
    this.element.style.textAlign = "right";
    this.element.style.zIndex = 200;
    this.element.style.width = style.width;
    for (var name in style) {
        this.element.style[name] = style[name];
    }
    parent.appendChild(this.element);
}

Label.prototype = {
    set: function(text) {
        this.element.innerHTML = text;
    }
}

function View(parent, scraper) {
    this.parent = parent;
    this.scraper = scraper;
    
    var canvasWidth = parent.clientWidth - 5;
    this.container = document.createElement("div");
    this.container.style.position = "relative";
    this.container.style.width = "95%";
    this.container.style.height = "92px";
    this.container.style.marginTop = "12px";
    this.messageLabel = new Label(this.container, {width: (canvasWidth - 4) + "px"});
    this.dateLabel = new Label(this.container, {position: "absolute",
                                                left: 0,
                                                bottom: "2px",
                                                height: "16px",
                                                width: (canvasWidth - 4) + "px"});
    this.canvas = new GraphCanvas(canvasWidth, 80);

    var self = this;
    self.update();
    setInterval(function() {
            self.update();
        }, 1000 * 60 * 60 * 3);
    
    this.container.appendChild(this.canvas.root);
    parent.appendChild(this.container);
}

View.prototype = {
    update: function() {
        var quota = this.scraper.quota();
        var accountName = this.scraper.account();
        if (quota && accountName) {
            var account = new Account(accountName);
            if (quota.search(/(\d+)\s*(MB|Mo).*?(\d+)\s*(MB|Mo)/) >= 0) {
                var used = parseInt(RegExp.$1);
                var total = parseInt(RegExp.$3);
                if (used > total) {
                    var tmp = used;
                    used = total;
                    total = tmp;
                }
                var today = SimpleDate.today();
                var startDate = SimpleDate.parse(account.getValue("startDate", today.toString()));
                var usedGraph = new Graph(startDate, "#00a", account.getValue("usedGraph", ""));
                var totalGraph = new Graph(startDate, "#f00", account.getValue("totalGraph", ""));
                
                //this.setMessage(used + " (" + Math.round(used / total * 100) + "%) / " + total + "");
                usedGraph.setValue(today, used);
                totalGraph.setValue(today, total);
                account.setValue("startDate", startDate.toString());
                account.setValue("usedGraph", usedGraph.serializeValues());
                account.setValue("totalGraph", totalGraph.serializeValues());
                this.showStatusAt(today, usedGraph, totalGraph);
                var self = this;
                this.canvas.draw([usedGraph, totalGraph], function(selectedDate) {
                        var adjustedDate;
                        if (selectedDate) {
                            usedGraph.getValue(selectedDate, function(d) {
                                    adjustedDate = d;
                                });
                            self.dateLabel.set(adjustedDate.displayDate());
                        } else {
                            adjustedDate = today;
                            self.dateLabel.set("");
                        }
                        self.showStatusAt(adjustedDate, usedGraph, totalGraph);
                    });
                //new Date(), [usedGraph, totalGraph], canvasWidth, 80);
                return;
            }
        }
        var errors = [];
        if (!accountName) errors.push("tên tài khoản");
        if (!quota) errors.push("hạn ngạch đĩa");
        this.messageLabel.set("Bị lỗi khi phân tích " + errors.join(" và ") + "!");
    },

    showStatusAt: function(date, usedGraph, totalGraph) {
        var used = usedGraph.getValue(date);
        var total = totalGraph.getValue(date);
        this.messageLabel.set(//adjustedDate.displayDate() + "<br />" +
          used + " (" + Math.floor(used / total * 100) + "%) / " + total + "");
    }
}

function GraphCanvas(width, height) {
    this.root = document.createElement("div");
    this.root.style.position = "absolute";
    this.root.style.width = width + "px";
    this.root.style.height = height + "px";
    this.root.style.bottom = "0";
    this.background = document.createElement("canvas");
    this.background.style.position = "absolute";
    this.background.style.zIndex = 100;
    this.background.setAttribute("width", width);
    this.background.setAttribute("height", height);
    this.canvas = document.createElement("canvas");
    this.canvas.style.position = "absolute";
    this.canvas.style.zIndex = 110;
    this.canvas.setAttribute("width", width);
    this.canvas.setAttribute("height", height);
    this.overlay = document.createElement("canvas");
    this.overlay.style.position = "absolute";
    this.overlay.style.zIndex = 210;
    this.overlay.setAttribute("width", width);
    this.overlay.setAttribute("height", height);
    this.root.appendChild(this.background);
    this.root.appendChild(this.canvas);
    this.root.appendChild(this.overlay);
    this.width = width;
    this.height = height;
    this.labels = [];
    this.top = 2.5;
    this.left = 2.5;
    this.bottom = this.height - 5.5;
    this.right = this.width - 4.5;
    this.graphHeight = this.bottom - this.top;
    this.graphWidth = this.right - this.left;

    var self = this;
    this.overlay.addEventListener("mousemove", function(event) {
            var x = 0, element = self.root;
            do { x += element.offsetLeft || 0; element = element.offsetParent; } while (element);
            self.select(event.clientX - x);
        }, true);
    this.overlay.addEventListener("mouseout", function(event) {
            self.unselect();
        }, true);
}

GraphCanvas.prototype = {
    unselect: function() {
        var ctx = this.overlay.getContext("2d");
        ctx.clearRect(0, 0, this.width, this.height);
        this.selectionUpdater(null);
    },

    select: function(x) {
        var ctx = this.overlay.getContext("2d");
        ctx.clearRect(0, 0, this.width, this.height);
        if (this.graphs && this.selectionUpdater) {
            var firstDate = this.graphs[0].firstDate;
            var len = this.graphs[0].values.length;
            var self = this;
            for (var g = 0; g < this.graphs.length; g++) {
                var graph = this.graphs[g];
                graph.getValue(firstDate.addDate(Math.round((x - this.left) / this.graphWidth * (len - 1))), function(date, value) {
                        var index = date.daysFrom(graph.firstDate);
                        var x = self.getX(index);
                        var y = self.getY(value);
                        ctx.strokeStyle = graph.style;
                        ctx.fillStyle = graph.style;
                        ctx.beginPath();
                        ctx.arc(x, y, 2, 0, Math.PI * 2, false);
                        ctx.stroke();
                        ctx.fill();
                        self.selectionUpdater(date);
                    });
            }
        }
    },

    draw: function(graphs, selectionUpdater) {
        this.graphs = [];
        for (var g = 0; g < graphs.length; g++) {
            this.graphs.push(graphs[g].drawable());
        }
        this.selectionUpdater = selectionUpdater;
        for (var i = 0; i < this.labels.length; i++) {
            this.root.removeChild(this.labels[i]);
        }
        this.labels = [];
        var ctx = this.canvas.getContext("2d");
        ctx.clearRect(0, 0, this.width, this.height);
        var bctx = this.background.getContext("2d");
        bctx.clearRect(0, 0, this.width, this.height);

        var top = this.top;
        var left = this.left;
        var bottom = this.bottom;
        var right = this.right;
        var graphHeight = this.graphHeight;
        var graphWidth = this.graphWidth;

        ctx.lineWidth = 1;
        ctx.strokeStyle = "#000";
        ctx.beginPath();
        ctx.moveTo(left, top);
        ctx.lineTo(left, bottom);
        ctx.lineTo(right, bottom);
        ctx.stroke();
        var grad = ctx.createLinearGradient(0, 0, 0, graphHeight);
        grad.addColorStop(0.0, "#ffffff");
        grad.addColorStop(1.0, "#e0e0e0");
        bctx.fillStyle = grad;
        bctx.fillRect(left, top, graphWidth, graphHeight);
        
        this.max = Math.max(this.graphs[0].max(), this.graphs[1].max());
        this.dataSize = this.graphs[0].values.length;
        
        var maxgb = Math.floor(this.max / 1024);
        var step = Math.ceil(maxgb / 4);
        
        for (var i = step; i <= maxgb; i += step) {
            var y = bottom - (i * 1024 * graphHeight / this.max);
            ctx.beginPath();
            ctx.moveTo(left, y);
            var color = Math.floor(180 + i * 1024 * 50 / this.max);
            ctx.strokeStyle = "rgba("+color+","+color+","+color+",1)";
            //ctx.strokeStyle = "rgba(0.8,0.8,0.8,1)";
            ctx.lineTo(right, y);
            ctx.stroke();
            var text = document.createElement("div");
            text.innerHTML = i + "GB";
            text.style.position = "absolute";
            text.style.left = left + 2 + "px";
            text.style.top = y + 1 + "px";
            text.style.fontSize = "9px";
            text.style.color = "#999999";
            text.style.zIndex = 105;
            this.labels.push(text);
            this.root.appendChild(text);
        }
        
        for (var g = 0; g < this.graphs.length; g++) {
            ctx.lineWidth = 2;
            var graph = this.graphs[g];
            var data = graph.values;
            ctx.beginPath();
            ctx.strokeStyle = graph.style;
            for (var i = 0; i < data.length; i++) {
                var value = data[i];
                if (i == 0) {
                    ctx.moveTo(this.getX(i), this.getY(value));
                } else {
                    if (value != null) {
                        ctx.lineTo(this.getX(i), this.getY(value));
                    }
                }
            }
            ctx.stroke();
        }
    },

    getX: function(index) {
        return this.left + index * this.graphWidth / (this.dataSize - 1);
    },

    getY: function(value) {
        return this.bottom - (value * this.graphHeight / this.max);
    }
}

window.addEventListener("load", function() {
        if (unsafeWindow.gmonkey) {
            var gmail;
            unsafeWindow.gmonkey.load("1.0", function(gmailAPI) {
                    gmail = gmailAPI;
                });
            waitFor(function() {
                    if (gmail) {
                        try {
                            gmail.getFooterElement();
                            return true;
                        } catch (e) {
                            GM_log("Đang chờ: gmail.getFooterElement() chưa chạy xong");
                            return false;
                        }
                    } else {
                        GM_log("Đang chờ: Gmail API chưa chạy xong");
                        return false;
                    }
                },
                function() {
                    var sidebar = gmail.getNavPaneElement();
                    var footer = gmail.getFooterElement();
                    var scraper = {
                        quota: function() {
                            return footer.innerHTML;
                        },
                        account: function() {
                            if (unsafeWindow.USER_EMAIL) {
                                // not present as of 26 Jun 2008?
                                return unsafeWindow.USER_EMAIL;
                            } else {
                                var guser = footer.ownerDocument.getElementById("guser");
                                if (guser) {
                                    return guser.firstChild.firstChild.innerHTML;
                                } else {
                                    return null;
                                }
                            }

                        }
                    };
                    new View(sidebar, scraper);
                });
        } else {
            var nav = document.getElementById("nav");
            if (nav) {
                new View(nav, {
                        quota: function() {
                            var fqList = document.evaluate("//div[@class='fq']",
                                                           document,
                                                           null,
                                                           XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
                                                           null);
                            if (fqList.snapshotLength > 0) {
                                var fq = fqList.snapshotItem(0);
                                return fq.innerHTML;
                            } else {
                                return null;
                            }
                        },
                            
                        account: function() {
                            var guser = document.getElementById("guser");
                            if (guser) {
                                // English version
                                return guser.firstChild.firstChild.innerHTML;
                            } else {
                                // Japanese version???
                                var list = document.evaluate("//td[@class='trb']", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                                if (list.snapshotLength > 0) {
                                    return list.snapshotItem(0).firstChild.innerHTML;
                                } else {
                                    return null;
                                }
                            }
                        }
                        
                });
            }
        }
        
        function waitFor(condition, proc) {
            var retries = 20;
            var intervalId = setInterval(function() {
                    if (condition()) {
                        clearInterval(intervalId);
                        proc();
                        return;
                    }
                    if (--retries <= 0) {
                        clearInterval(intervalId);
                    }
                }, 1000);
        }

    }, true);