Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save gwthompson/9d13b521684ff7c489479e61108fe640 to your computer and use it in GitHub Desktop.
Save gwthompson/9d13b521684ff7c489479e61108fe640 to your computer and use it in GitHub Desktop.
Widget con HTML y CSS en Scriptable
const js = `(function (global) {
"use strict";
var util = newUtil();
// Default impl options
var defaultOptions = {
// Default is to fail on error, no placeholder
imagePlaceholder: undefined,
// Default cache bust is false, it will use the cache
cacheBust: false,
};
var domtoimage = {
toSvg: toSvg,
toPng: toPng,
toJpeg: toJpeg,
toBlob: toBlob,
toPixelData: toPixelData,
impl: {
options: {},
util: util,
},
};
if (typeof module !== "undefined") module.exports = domtoimage;
else global.domtoimage = domtoimage;
/**
* @param {Node} node - The DOM Node object to render
* @param {Object} options - Rendering options
* @param {Function} options.filter - Should return true if passed node should be included in the output
* (excluding node means excluding it's children as well). Not called on the root node.
* @param {String} options.bgcolor - color for the background, any valid CSS color value.
* @param {Number} options.width - width to be applied to node before rendering.
* @param {Number} options.height - height to be applied to node before rendering.
* @param {Object} options.style - an object whose properties to be copied to node's style before rendering.
* @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only),
defaults to 1.0.
* @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch
* @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url
* @return {Promise} - A promise that is fulfilled with a SVG image data URL
* */
function toSvg(node, options) {
options = options || {};
copyOptions(options);
return (
Promise.resolve(node)
.then(function (node) {
return cloneNode(node, options.filter, true);
})
//.then(embedFonts)
//.then(inlineImages)
.then(applyOptions)
.then(function (clone) {
return makeSvgDataUri(
clone,
options.width || util.width(node),
options.height || util.height(node)
);
})
);
function applyOptions(clone) {
if (options.bgcolor) clone.style.backgroundColor = options.bgcolor;
if (options.width) clone.style.width = options.width + "px";
if (options.height) clone.style.height = options.height + "px";
if (options.style)
Object.keys(options.style).forEach(function (property) {
clone.style[property] = options.style[property];
});
return clone;
}
}
/**
* @param {Node} node - The DOM Node object to render
* @param {Object} options - Rendering options, @see {@link toSvg}
* @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data.
* */
function toPixelData(node, options) {
return draw(node, options || {}).then(function (canvas) {
return canvas
.getContext("2d")
.getImageData(0, 0, util.width(node), util.height(node)).data;
});
}
/**
* @param {Node} node - The DOM Node object to render
* @param {Object} options - Rendering options, @see {@link toSvg}
* @return {Promise} - A promise that is fulfilled with a PNG image data URL
* */
function toPng(node, options) {
return draw(node, options || {}).then(function (canvas) {
return canvas.toDataURL();
});
}
/**
* @param {Node} node - The DOM Node object to render
* @param {Object} options - Rendering options, @see {@link toSvg}
* @return {Promise} - A promise that is fulfilled with a JPEG image data URL
* */
function toJpeg(node, options) {
options = options || {};
return draw(node, options).then(function (canvas) {
return canvas.toDataURL("image/jpeg", options.quality || 1.0);
});
}
/**
* @param {Node} node - The DOM Node object to render
* @param {Object} options - Rendering options, @see {@link toSvg}
* @return {Promise} - A promise that is fulfilled with a PNG image blob
* */
function toBlob(node, options) {
return draw(node, options || {}).then(util.canvasToBlob);
}
function copyOptions(options) {
// Copy options to impl options for use in impl
if (typeof options.imagePlaceholder === "undefined") {
domtoimage.impl.options.imagePlaceholder =
defaultOptions.imagePlaceholder;
} else {
domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder;
}
if (typeof options.cacheBust === "undefined") {
domtoimage.impl.options.cacheBust = defaultOptions.cacheBust;
} else {
domtoimage.impl.options.cacheBust = options.cacheBust;
}
}
function draw(domNode, options) {
return toSvg(domNode, options)
.then(util.makeImage)
.then(util.delay(100))
.then(function (image) {
var canvas = newCanvas(domNode);
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
});
function newCanvas(domNode) {
var canvas = document.createElement("canvas");
canvas.width = options.width || util.width(domNode);
canvas.height = options.height || util.height(domNode);
if (options.bgcolor) {
var ctx = canvas.getContext("2d");
ctx.fillStyle = options.bgcolor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
return canvas;
}
}
function cloneNode(node, filter, root) {
if (!root && filter && !filter(node)) return Promise.resolve();
return Promise.resolve(node)
.then(makeNodeCopy)
.then(function (clone) {
return cloneChildren(node, clone, filter);
})
.then(function (clone) {
return processClone(node, clone);
});
function makeNodeCopy(node) {
if (node instanceof HTMLCanvasElement)
return util.makeImage(node.toDataURL());
return node.cloneNode(false);
}
function cloneChildren(original, clone, filter) {
var children = original.childNodes;
if (children.length === 0) return Promise.resolve(clone);
return cloneChildrenInOrder(
clone,
util.asArray(children),
filter
).then(function () {
return clone;
});
function cloneChildrenInOrder(parent, children, filter) {
var done = Promise.resolve();
children.forEach(function (child) {
done = done
.then(function () {
return cloneNode(child, filter);
})
.then(function (childClone) {
if (childClone) parent.appendChild(childClone);
});
});
return done;
}
}
function processClone(original, clone) {
if (!(clone instanceof Element)) return clone;
return Promise.resolve()
.then(cloneStyle)
.then(clonePseudoElements)
.then(copyUserInput)
.then(fixSvg)
.then(function () {
return clone;
});
function cloneStyle() {
copyStyle(window.getComputedStyle(original), clone.style);
function copyStyle(source, target) {
if (source.cssText) target.cssText = source.cssText;
else copyProperties(source, target);
function copyProperties(source, target) {
util.asArray(source).forEach(function (name) {
target.setProperty(
name,
source.getPropertyValue(name),
source.getPropertyPriority(name)
);
});
}
}
}
function clonePseudoElements() {
[":before", ":after"].forEach(function (element) {
clonePseudoElement(element);
});
function clonePseudoElement(element) {
var style = window.getComputedStyle(original, element);
var content = style.getPropertyValue("content");
if (content === "" || content === "none") return;
var className = util.uid();
clone.className = clone.className + " " + className;
var styleElement = document.createElement("style");
styleElement.appendChild(
formatPseudoElementStyle(className, element, style)
);
clone.appendChild(styleElement);
function formatPseudoElementStyle(
className,
element,
style
) {
var selector = "." + className + ":" + element;
var cssText = style.cssText
? formatCssText(style)
: formatCssProperties(style);
return document.createTextNode(
selector + "{" + cssText + "}"
);
function formatCssText(style) {
var content = style.getPropertyValue("content");
return style.cssText + " content: " + content + ";";
}
function formatCssProperties(style) {
return (
util
.asArray(style)
.map(formatProperty)
.join("; ") + ";"
);
function formatProperty(name) {
return (
name +
": " +
style.getPropertyValue(name) +
(style.getPropertyPriority(name)
? " !important"
: "")
);
}
}
}
}
}
function copyUserInput() {
if (original instanceof HTMLTextAreaElement)
clone.innerHTML = original.value;
if (original instanceof HTMLInputElement)
clone.setAttribute("value", original.value);
}
function fixSvg() {
if (!(clone instanceof SVGElement)) return;
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
if (!(clone instanceof SVGRectElement)) return;
["width", "height"].forEach(function (attribute) {
var value = clone.getAttribute(attribute);
if (!value) return;
clone.style.setProperty(attribute, value);
});
}
}
}
function embedFonts(node) {
return fontFaces.resolveAll().then(function (cssText) {
var styleNode = document.createElement("style");
node.appendChild(styleNode);
styleNode.appendChild(document.createTextNode(cssText));
return node;
});
}
function inlineImages(node) {
return images.inlineAll(node).then(function () {
return node;
});
}
function makeSvgDataUri(node, width, height) {
return Promise.resolve(node)
.then(function (node) {
node.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
return new XMLSerializer().serializeToString(node);
})
.then(util.escapeXhtml)
.then(function (xhtml) {
return (
'<foreignObject x="0" y="0" width="100%" height="100%">' +
xhtml +
"</foreignObject>"
);
})
.then(function (foreignObject) {
return (
'<svg xmlns="http://www.w3.org/2000/svg" width="' +
width +
'" height="' +
height +
'">' +
foreignObject +
"</svg>"
);
})
.then(function (svg) {
return "data:image/svg+xml;charset=utf-8," + svg;
});
}
function newUtil() {
return {
escape: escape,
parseExtension: parseExtension,
mimeType: mimeType,
dataAsUrl: dataAsUrl,
isDataUrl: isDataUrl,
canvasToBlob: canvasToBlob,
resolveUrl: resolveUrl,
getAndEncode: getAndEncode,
uid: uid(),
delay: delay,
asArray: asArray,
escapeXhtml: escapeXhtml,
makeImage: makeImage,
width: width,
height: height,
};
function mimes() {
/*
* Only WOFF and EOT mime types for fonts are 'real'
* see http://www.iana.org/assignments/media-types/media-types.xhtml
*/
var WOFF = "application/font-woff";
var JPEG = "image/jpeg";
return {
woff: WOFF,
woff2: WOFF,
ttf: "application/font-truetype",
eot: "application/vnd.ms-fontobject",
png: "image/png",
jpg: JPEG,
jpeg: JPEG,
gif: "image/gif",
tiff: "image/tiff",
svg: "image/svg+xml",
};
}
function parseExtension(url) {
return "png";
}
function mimeType(url) {
var extension = parseExtension(url).toLowerCase();
return mimes()[extension] || "";
}
function isDataUrl(url) {
return url.search(/^(data:)/) !== -1;
}
function toBlob(canvas) {
return new Promise(function (resolve) {
var binaryString = window.atob(
canvas.toDataURL().split(",")[1]
);
var length = binaryString.length;
var binaryArray = new Uint8Array(length);
for (var i = 0; i < length; i++)
binaryArray[i] = binaryString.charCodeAt(i);
resolve(
new Blob([binaryArray], {
type: "image/png",
})
);
});
}
function canvasToBlob(canvas) {
if (canvas.toBlob)
return new Promise(function (resolve) {
canvas.toBlob(resolve);
});
return toBlob(canvas);
}
function resolveUrl(url, baseUrl) {
var doc = document.implementation.createHTMLDocument();
var base = doc.createElement("base");
doc.head.appendChild(base);
var a = doc.createElement("a");
doc.body.appendChild(a);
base.href = baseUrl;
a.href = url;
return a.href;
}
function uid() {
var index = 0;
return function () {
return "u" + fourRandomChars() + index++;
function fourRandomChars() {
/* see http://stackoverflow.com/a/6248722/2519373 */
return (
"0000" +
((Math.random() * Math.pow(36, 4)) << 0).toString(36)
).slice(-4);
}
};
}
function makeImage(uri) {
return new Promise(function (resolve, reject) {
var image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = reject;
image.src = uri;
});
}
function getAndEncode(url) {
var TIMEOUT = 30000;
return new Promise(function (resolve) {
var request = new XMLHttpRequest();
request.onreadystatechange = done;
request.ontimeout = timeout;
request.responseType = "blob";
request.timeout = TIMEOUT;
request.open("GET", url, true);
request.send();
var placeholder;
if (domtoimage.impl.options.imagePlaceholder) {
var split = domtoimage.impl.options.imagePlaceholder.split(
/,/
);
if (split && split[1]) {
placeholder = split[1];
}
}
function done() {
if (request.readyState !== 4) return;
if (request.status !== 200) {
if (placeholder) {
resolve(placeholder);
} else {
fail(
"cannot fetch resource: " +
url +
", status: " +
request.status
);
}
return;
}
var encoder = new FileReader();
encoder.onloadend = function () {
var content = encoder.result.split(/,/)[1];
resolve(content);
};
encoder.readAsDataURL(request.response);
}
function timeout() {
if (placeholder) {
resolve(placeholder);
} else {
fail(
"timeout of " +
TIMEOUT +
"ms occured while fetching resource: " +
url
);
}
}
function fail(message) {
console.error(message);
resolve("");
}
});
}
function dataAsUrl(content, type) {
return "data:" + type + ";base64," + content;
}
function escape(string) {
return "";
}
function delay(ms) {
return function (arg) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(arg);
}, ms);
});
};
}
function asArray(arrayLike) {
var array = [];
var length = arrayLike.length;
for (var i = 0; i < length; i++) array.push(arrayLike[i]);
return array;
}
function escapeXhtml(string) {
return string;
}
function width(node) {
var leftBorder = px(node, "border-left-width");
var rightBorder = px(node, "border-right-width");
return node.scrollWidth + leftBorder + rightBorder;
}
function height(node) {
var topBorder = px(node, "border-top-width");
var bottomBorder = px(node, "border-bottom-width");
return node.scrollHeight + topBorder + bottomBorder;
}
function px(node, styleProperty) {
var value = window
.getComputedStyle(node)
.getPropertyValue(styleProperty);
return parseFloat(value.replace("px", ""));
}
}
})(this);
const html = "<html> \
<head> \
<style> \
body { \
margin: 0; \
background-color: cyan; \
width: 500px; \
height: 500px; \
} \
header { \
width: 100%; \
height: 100px; \
background-color: red; \
} \
.container { \
width: 100%; \
height: 400px; \
background-color: white; \
text-align: center; \
font-size: 50px; \
} \
</style> \
</head> \
<body> \
<header></header> \
<div class='container'>Widget construido con HTML y CSS, en iOS, si. En tu cara Jobs.</div> \
</body> \
</html>";
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const iframedoc = iframe.contentDocument || iframe.contentWindow.document;
iframedoc.body.innerHTML = html;
const blobToBase64 = (blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise((resolve) => {
reader.onloadend = () => {
resolve(reader.result);
};
});
};
setTimeout(function() {
domtoimage.toBlob(iframedoc.body).then(async (blob) => {
completion(await blobToBase64(blob));
});
}, 1)`;
const wv = new WebView();
const test = await wv.evaluateJavaScript(js, true);
log(test);
const widget = new ListWidget();
const imgReqBq = await new Request(test);
const imgBq = await imgReqBq.loadImage();
widget.backgroundImage = imgBq;
if (config.runsInWidget) {
Script.setWidget(widget);
Script.complete();
} else {
widget.presentLarge();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment