25 сеп 2009

Първа среща с Ubiquity

Категория: Firefox,HTML5Lucho @ 01:40

Тези дни попаднах на поредното интересно нещо от Mozilla – Ubiquity. Накратко това е плъгин, който осигурява лесно и кратко писане на команди автоматизиращи последователност от действия или предоставящи интерфейс към услуга. На разположение е терминал, в който човек избира команда, извежда се малко панелче, в което се вижда резултата… и това е в общи линий. Това с което бие bookmarklet-ите е вграденото jQuery, възможността да има стотици команди (bookmark лентата е ограничена от към резолюция :P), предлагане на шаблонен интерфейс (попълвате дескрипшън, хелп, etc и те се появяват на правилното място в правилното време), прави Ajax заявки към произволен сървър, има разни шантави тематични аутокъмплийт списъци (nouns), notification bar-че  и още куп готини работи. Пише се на Javascript и Python (само за OS X и Linux, Windows не е стандартно с Python – така че няма как :-( ) и кода излиза умопомрачително кратък, главно заради хелпърите, които идват с Ubiquity и jQuery. Част от стандартните команди, които са в инсталацията са twitter клиент, бързо търсене на различни места, локиращо по IP скриптче, което дава линк към google maps, превод in-place на фрагменти от страница, скъсяване на линкове чрез tinyurl и други.

Разбира се, аз като open-minded developer на какъвто се правя напоследък, няма как да пропусна възможността да напиша нещо малко и от сърце за Ubiquity, а и ScreenShotMe стана толкова „популярен“ (трябва да измислят четворни кавички, двойните неотразяват добре реалността в този случай), че съм готов вече на всякакви експерименти – включително и писане на Ubiquity команда, която да прави снимка на текущата страница и да я праща (в ада :D) на сървиса. След около ден рисърч и зяпане из сорсовете на Firefox, се убедих че nsITransferable неподдържа „image/bmp“ data flavor – може би защото от BMP формата лъха твърде много на Microsoft, кой знае. Това директно изпотроши първоначалната ми идея юзера да цъка print screen и командата да изпраща целия екран. Но всяко зло за добро – докато разглеждах имплементацията на хелпърите на Ubiquity се натъкнах на нещо яко – начин да изрисуваш window-ски content в canvas. Това явление е тотална измишльотина на Mozilla, защото в HTML5 Canvas draft-та няма такива неща, но все пак спаси положението. Вярно – няма да записвам целия екран, но пък когато човек иска да print screen-не browser-а си, обикновено не иска да му се виждат всички табове например, така че от подобна функционалност, която да предлага снимка само на уеб страницата има смисъл, а и ще върви и под OS X (за разлика от предния път :-D).

// Най-добре натиснете "<>" горе вдясно на страницата, за да се разшири.
// Така създаваме нов запис на команда, която да ползваме от конзолата
CmdUtils.CreateCommand({

// име, аргументи, описание, превю, хелп са задължителни
  names: ["shotit"],
  arguments: [{role: 'object', nountype: noun_arb_text, label: "image format"}],
  description: _("Publishes the current browser snapshot on the web and puts a link to it in the clipboard."),
  homepage: "http://dailyffs.com/index.php/share-screen/",
  author: {name: "Lucho Yankov"},
  license: "GPL",
// preview може и да е метод, който да започне изпълнение веднага щом селектирате командата (без да натискате enter или да я изпълнявате даже), но на мен ми трябва само статичен текст
  preview: _("Publishes the current browser snapshot on the web and puts a link to it in the clipboard."),
  help: _("To specify the image format just put 'jpeg' or 'png' as command's parameter, if no format is specified and the image is smaller than 1200KB it will be saved as PNG, otherwise the JPEG format will be used."),

// execute е кодът, който ще се изпълни при стартиране на командата
  execute: function(args) {
    var window = CmdUtils.getWindow();
// Следващите няколко реда са взаимствани до голяма степен от имплементацията на CmdUtils.getWindowSnapshot
    var canvas = CmdUtils.getHiddenWindow().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
    canvas.mozOpaque = true; // Дали canvas-ът да поддържа alpha или само солиден цвял (ускорява някои операции)
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
// Шантавият метод на canvas, който рисува прозорци - INSANE!
    canvas.getContext("2d").drawWindow(window, window.scrollX, window.scrollY, window.innerWidth, window.innerWidth, "rgb(255,255,255)");
    var data = null;
// Проверка, дали има експлицитно зададен формат на изображението и ако не, дали то не е твърде голямо за да го правим на PNG и директно да го запазим като JPEG.
// Параметрите трябва да са jpeg, jpg, j, png, p
    if (/^j(pe?g)?\s*$/i.test(args.object.text)) {
        data = canvas.toDataURL("image/jpeg");
    } else if (/^p(ng)?\s*$/i.test(args.object.text)) {
        data = canvas.toDataURL("image/png");
    } else {
        data = canvas.toDataURL("image/png");
        if (data.length > 1600*1024) data = canvas.toDataURL("image/jpeg");
    }
// Показваме в notification bar-а, колко данни се изпращат, за да придобие представа потребителя, дали ще чака докато остарее или повече
    displayMessage(_("Sending ") + parseInt(data.length/1024+1) + "KB...", this);
    MAIN_URL = "http://dailyffs.com/shotme/";
// Изпращаме изображението към сървъра и когато върне като отговор ключа индексиращ картинката, сглобяваме линк и го пъхаме в клипборда и в notification bar-а
    jQuery.post(MAIN_URL, "image="+data, function(key) {
        if (key == "ERROR") return;
        var url = MAIN_URL + "?" + key;
        Utils.clipboard.text = url;
        displayMessage({text: url, onclick: function(){window.open(url);}}, this);
      }
    );
  }
});

Резултата от усилията ми е качен в Git, напълно отворен и поощрен за разширение – http://gist.github.com/192995 , версията на Ubiquity за която го писах е 0.5.4 (в случай, че нещо при някой невърви).

Ако някой има идеи, предложения или питания да заповяда – коментарите са за това ;-)

// така създаваме нов запис на команда, която да ползваме от конзолата
CmdUtils.CreateCommand({

// име, аргументи, описание, превю, хелп са задължителни
names: [„shotit“],
arguments: [{role: ‘object’, nountype: noun_arb_text, label: „image format“}],
description: _(„Publishes the current browser snapshot on the web and puts a link to it in the clipboard.“),
homepage: „http://dailyffs.com/index.php/share-screen/“,
author: {name: „Lucho Yankov“},
license: „GPL“,
// preview може и да е метод, който да започне изпълнение веднага щом селектирате командата
// (без да натискате enter или да я изпълнявате даже), но на мен ми трябва само статичен текст
preview: _(„Publishes the current browser snapshot on the web and puts a link to it in the clipboard.“),
help: _(„To specify the image format just put ‘jpeg’ or ‘png’ as command’s parameter, if no format is specified and the image is smaller than 1200KB it will be saved as PNG, otherwise the JPEG format will be used.“),

// execute е кода, който ще се изпълни при стартиране на командата
execute: function(args) {
var window = CmdUtils.getWindow();
// следващите няколко реда са взаимствани до голяма степен от имплементацията на CmdUtils.getWindowSnapshot
var canvas = CmdUtils.getHiddenWindow().document.createElementNS(„http://www.w3.org/1999/xhtml“, „canvas“);
canvas.mozOpaque = true;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// шантавият метод на canvas, който рисува прозорци – INSANE!
canvas.getContext(„2d“).drawWindow(window, window.scrollX, window.scrollY, window.innerWidth, window.innerWidth, „rgb(255,255,255)“);
var data = null;
// проверка, дали има експлицитно зададен формат на изображението и ако не,
// дали то не е твърде голямо за да го правим на PNG и директно да го запазим като JPEG.
// параметрите трябва да са jpeg, jpg, j, png, p
if (/^j(pe?g)?\s*$/i.test(args.object.text)) {
data = canvas.toDataURL(„image/jpeg“);
} else if (/^p(ng)?\s*$/i.test(args.object.text)) {
data = canvas.toDataURL(„image/png“);
} else {
data = canvas.toDataURL(„image/png“);
if (data.length > 1600*1024) data = canvas.toDataURL(„image/jpeg“);
}
// Показваме в notification bar-а, колко данни се изпращат, за да придобие представа потребителя,
// дали ще чака докато остарее или повече ;-)
displayMessage(_(„Sending „) + parseInt(data.length/1024+1) + „KB…“, this);
MAIN_URL = „http://dailyffs.com/shotme/“;
// изпращаме изображението към сървъра и когато върне като отговор ключа индексиращ картинката го сглобяваме
// и го пъхаме в клипборда и в notification bar-а
jQuery.post(MAIN_URL, „image=“+data, function(key) {
if (key == „ERROR“) return;
var url = MAIN_URL + „?“ + key;
Utils.clipboard.text = url;
displayMessage({text: url, onclick: function(){window.open(url);}}, this);
}
);
}
});