commit
7791aee721
|
@ -1,5 +1,9 @@
|
||||||
# CHANGELOG.md
|
# CHANGELOG.md
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
Added SVG and PNG downloads
|
||||||
|
|
||||||
## v1.0.2
|
## v1.0.2
|
||||||
|
|
||||||
* Fixed bug which was stopping slanted grids
|
* Fixed bug which was stopping slanted grids
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
|
||||||
|
import * as resvg from 'npm:@resvg/resvg-wasm';
|
||||||
|
|
||||||
|
|
||||||
|
const xmlns = "http://www.w3.org/2000/xmlns/";
|
||||||
|
const xlinkns = "http://www.w3.org/1999/xlink";
|
||||||
|
const svgns = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
// adapted from the DOM.download method as per this issue:
|
||||||
|
|
||||||
|
// https://github.com/observablehq/framework/issues/906
|
||||||
|
|
||||||
|
|
||||||
|
export function download(value, name = "untitled", label = "Save") {
|
||||||
|
const a = document.createElement("a");
|
||||||
|
const b = a.appendChild(document.createElement("button"));
|
||||||
|
b.textContent = label;
|
||||||
|
a.download = name;
|
||||||
|
|
||||||
|
async function reset() {
|
||||||
|
await new Promise(requestAnimationFrame);
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
a.removeAttribute("href");
|
||||||
|
b.textContent = label;
|
||||||
|
b.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.onclick = async event => {
|
||||||
|
console.log("clicked download");
|
||||||
|
b.disabled = true;
|
||||||
|
if (a.href) {
|
||||||
|
console.log(`already saved: ${a.href}`);
|
||||||
|
return reset(); // Already saved.
|
||||||
|
}
|
||||||
|
b.textContent = "Saving…";
|
||||||
|
try {
|
||||||
|
console.log("awaiting value function");
|
||||||
|
const object = await (typeof value === "function" ? value() : value);
|
||||||
|
console.log(object);
|
||||||
|
b.textContent = "Download";
|
||||||
|
a.href = URL.createObjectURL(object); // eslint-disable-line require-atomic-updates
|
||||||
|
console.log(`url = ${a.href}`);
|
||||||
|
} catch (ignore) {
|
||||||
|
b.textContent = label;
|
||||||
|
}
|
||||||
|
if (event.eventPhase) return reset(); // Already downloaded.
|
||||||
|
b.disabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function svg_to_string(svg) {
|
||||||
|
svg = svg.cloneNode(true);
|
||||||
|
svg.setAttributeNS(xmlns, "xmlns", svgns);
|
||||||
|
svg.setAttributeNS(xmlns, "xmlns:xlink", xlinkns);
|
||||||
|
const serializer = new window.XMLSerializer;
|
||||||
|
return serializer.serializeToString(svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function download_as_svg(svg) {
|
||||||
|
console.log("HEY download_as_svg");
|
||||||
|
const str = svg_to_string(svg);
|
||||||
|
return new Blob([str], {type: "image/svg+xml"})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function download_as_png (svg) {
|
||||||
|
// The Wasm must be initialized first
|
||||||
|
|
||||||
|
const svgstr = svg_to_string(svg);
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
fitTo: {
|
||||||
|
mode: 'width', // If you need to change the size
|
||||||
|
value: 400,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const resvgJS = new resvg.Resvg(svgstr, opts)
|
||||||
|
const pngData = resvgJS.render(svgstr, opts)
|
||||||
|
const pngBuffer = pngData.asPng();
|
||||||
|
return new Blob([pngBuffer], { type: 'image/png' });
|
||||||
|
}
|
||||||
|
|
30
src/index.md
30
src/index.md
|
@ -2,9 +2,11 @@
|
||||||
toc: false
|
toc: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h1>poptimal</h1>
|
<h1>poptimal</h1>
|
||||||
|
|
||||||
<p>v1.0.2 | by <a href="https://mikelynch.org">mike lynch</a> | <a href="https://aus.social/@mikelynch">@mikelynch@aus.social</a> | <a href="https://git.tilde.town/bombinans/poptimal">source</a></p>
|
<p>v1.1.0 | by <a href="https://mikelynch.org">mike lynch</a> | <a href="https://aus.social/@mikelynch">@mikelynch@aus.social</a> | <a href="https://git.tilde.town/bombinans/poptimal">source</a></p>
|
||||||
|
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid grid-cols-2">
|
||||||
|
|
||||||
|
@ -14,8 +16,13 @@ toc: false
|
||||||
|
|
||||||
import {RADIUS_OPTS, DotMaker} from './components/dots.js';
|
import {RADIUS_OPTS, DotMaker} from './components/dots.js';
|
||||||
import {PALETTES, DotControls} from './components/controls.js';
|
import {PALETTES, DotControls} from './components/controls.js';
|
||||||
|
import {download, download_as_svg, download_as_png} from './components/download.js';
|
||||||
import random from "npm:random";
|
import random from "npm:random";
|
||||||
|
|
||||||
|
import * as resvg from 'npm:@resvg/resvg-wasm';
|
||||||
|
|
||||||
|
await resvg.initWasm(fetch('https://unpkg.com/@resvg/resvg-wasm/index_bg.wasm'));
|
||||||
|
|
||||||
const CELL = 10;
|
const CELL = 10;
|
||||||
const MAG = 2;
|
const MAG = 2;
|
||||||
const WIDTH = 20;
|
const WIDTH = 20;
|
||||||
|
@ -149,10 +156,27 @@ dots_g2.selectAll("circle")
|
||||||
|
|
||||||
display(svg.node());
|
display(svg.node());
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</div>
|
```js
|
||||||
|
|
||||||
|
display(download(() => {
|
||||||
|
const thing = download_as_svg(svg.node())
|
||||||
|
return thing;
|
||||||
|
}, "poptimal.svg", "Save as SVG"));
|
||||||
|
|
||||||
|
display(download(() => {
|
||||||
|
console.log("PNG value");
|
||||||
|
const thing = download_as_png(svg.node())
|
||||||
|
return thing;
|
||||||
|
}, "poptimal.png", "Save as PNG"));
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
(PNGs made with <a href="https://github.com/thx/resvg-js">resvg-wasm</a> in-browser)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
Loading…
Reference in New Issue