Merge branch 'feature-bot-script' into rc-1.1.1
This commit is contained in:
		
						commit
						72d50ca58a
					
				
							
								
								
									
										979
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										979
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -10,8 +10,15 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@observablehq/framework": "^1.13.0",
 | 
					    "@observablehq/framework": "^1.13.0",
 | 
				
			||||||
 | 
					    "@resvg/resvg-js": "^2.6.2",
 | 
				
			||||||
 | 
					    "d3": "^7.9.0",
 | 
				
			||||||
 | 
					    "d3-color": "^3.1.0",
 | 
				
			||||||
    "d3-dsv": "^3.0.1",
 | 
					    "d3-dsv": "^3.0.1",
 | 
				
			||||||
    "d3-time-format": "^4.1.0"
 | 
					    "d3-time-format": "^4.1.0",
 | 
				
			||||||
 | 
					    "jsdom": "^26.0.0",
 | 
				
			||||||
 | 
					    "lodash.shuffle": "^4.2.0",
 | 
				
			||||||
 | 
					    "random": "^5.1.1",
 | 
				
			||||||
 | 
					    "yargs": "^17.7.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "rimraf": "^5.0.5"
 | 
					    "rimraf": "^5.0.5"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										113
									
								
								poptimal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								poptimal.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					import {  Resvg } from "@resvg/resvg-js";
 | 
				
			||||||
 | 
					import { promises } from "fs";
 | 
				
			||||||
 | 
					import { JSDOM } from "jsdom";
 | 
				
			||||||
 | 
					import * as d3 from "d3";
 | 
				
			||||||
 | 
					import yargs from "yargs/yargs";
 | 
				
			||||||
 | 
					import { hideBin } from "yargs/helpers";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import random from "random";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const xmlns = "http://www.w3.org/2000/xmlns/";
 | 
				
			||||||
 | 
					const xlinkns = "http://www.w3.org/1999/xlink";
 | 
				
			||||||
 | 
					const svgns = "http://www.w3.org/2000/svg";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {RADIUS_OPTS, DotMaker} from './src/components/dots.js';
 | 
				
			||||||
 | 
					import {PALETTES} from './src/components/palettes.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CELL = 10;
 | 
				
			||||||
 | 
					const MAG = 2;
 | 
				
			||||||
 | 
					const WIDTH = 20;
 | 
				
			||||||
 | 
					const HEIGHT = WIDTH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function poptimal_svg() {
 | 
				
			||||||
 | 
						const window = new JSDOM().window;
 | 
				
			||||||
 | 
						const document = window.document;
 | 
				
			||||||
 | 
						const container = d3.select(document.body).append("div");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const dm = new DotMaker(WIDTH);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const m1 = random.choice([1, 2, 3, 4, 5]);
 | 
				
			||||||
 | 
						const n1 = random.choice([1, 2, 3, 4, 5]);
 | 
				
			||||||
 | 
						const m2 = random.choice([1, 2, 3, 4, 5]);
 | 
				
			||||||
 | 
						const n2 = random.choice([1, 2, 3, 4, 5]);
 | 
				
			||||||
 | 
						const palette_fn = random.choice(Array.from(PALETTES.values()));
 | 
				
			||||||
 | 
						const palette = palette_fn();
 | 
				
			||||||
 | 
						const bg = palette[0];
 | 
				
			||||||
 | 
						const fg1 = palette[1];
 | 
				
			||||||
 | 
						const fg2 = palette[2];
 | 
				
			||||||
 | 
						const f1 = random.choice(RADIUS_OPTS);
 | 
				
			||||||
 | 
						const f2 = random.choice(RADIUS_OPTS);
 | 
				
			||||||
 | 
						const r1 = random.float(0, 0.4);
 | 
				
			||||||
 | 
						const r2 = random.float(0, 0.4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const dots1 = dm.dots(1 / m1, n1);
 | 
				
			||||||
 | 
						const dots2 = dm.dots(1 / m2, n2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const svg = container.append("svg")
 | 
				
			||||||
 | 
						  .attr("width", WIDTH * CELL * MAG)
 | 
				
			||||||
 | 
						  .attr("height", HEIGHT * CELL * MAG)
 | 
				
			||||||
 | 
						  .attr("viewBox", [ 0, 0, WIDTH, HEIGHT ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const background = svg.append("rect")
 | 
				
			||||||
 | 
						  .attr("x", 0)
 | 
				
			||||||
 | 
						  .attr("y", 0)
 | 
				
			||||||
 | 
						  .attr("width", WIDTH)
 | 
				
			||||||
 | 
						  .attr("height", WIDTH)
 | 
				
			||||||
 | 
						  .attr("fill", bg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const dots_g1 = svg.append("g")
 | 
				
			||||||
 | 
						  .attr("id", "dots1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dots_g1.selectAll("circle")
 | 
				
			||||||
 | 
						  .data(dots1)
 | 
				
			||||||
 | 
						  .join("circle")
 | 
				
			||||||
 | 
						  .attr("r", (d) => dm.radius(d, f1, r1))
 | 
				
			||||||
 | 
						  .attr("fill", fg1)
 | 
				
			||||||
 | 
						  .attr("cx", (d) => d.x)
 | 
				
			||||||
 | 
						  .attr("cy", (d) => d.y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const dots_g2 = svg.append("g")
 | 
				
			||||||
 | 
						  .attr("id", "dots2");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dots_g2.selectAll("circle")
 | 
				
			||||||
 | 
						  .data(dots2)
 | 
				
			||||||
 | 
						  .join("circle")
 | 
				
			||||||
 | 
						  .attr("r", (d) => dm.radius(d, f2, r2))
 | 
				
			||||||
 | 
						  .attr("fill", fg2)
 | 
				
			||||||
 | 
						  .attr("cx", (d) => d.x)
 | 
				
			||||||
 | 
						  .attr("cy", (d) => d.y);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
						const node = svg.node();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  	node.setAttributeNS(xmlns, "xmlns", svgns);
 | 
				
			||||||
 | 
					  	node.setAttributeNS(xmlns, "xmlns:xlink", xlinkns);
 | 
				
			||||||
 | 
					  	const serializer = new window.XMLSerializer;
 | 
				
			||||||
 | 
					  	return serializer.serializeToString(node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function main() {
 | 
				
			||||||
 | 
						const argv = yargs(hideBin(process.argv))
 | 
				
			||||||
 | 
							.usage("Usage: -w WIDTH -o OUTPUT_PNG")
 | 
				
			||||||
 | 
							.default('w', 1200)
 | 
				
			||||||
 | 
							.default('o', 'poptimal.png').argv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const svg = poptimal_svg(argv.w);
 | 
				
			||||||
 | 
						const opts = {
 | 
				
			||||||
 | 
					    	background: 'rgba(255, 255, 255, 1.0)',
 | 
				
			||||||
 | 
					    	fitTo: {
 | 
				
			||||||
 | 
					      		mode: 'width',
 | 
				
			||||||
 | 
					      		value: argv.w,
 | 
				
			||||||
 | 
					    	},
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  	const resvg = new Resvg(svg, opts)
 | 
				
			||||||
 | 
					 	const pngData = resvg.render()
 | 
				
			||||||
 | 
					  	const pngBuffer = pngData.asPng()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  	await promises.writeFile(argv.o, pngBuffer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main();
 | 
				
			||||||
							
								
								
									
										90
									
								
								src/components/controls.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										90
									
								
								src/components/controls.js
									
									
									
									
										vendored
									
									
								
							@ -2,90 +2,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import * as Inputs from "npm:@observablehq/inputs";
 | 
					import * as Inputs from "npm:@observablehq/inputs";
 | 
				
			||||||
import random from "npm:random";
 | 
					import random from "npm:random";
 | 
				
			||||||
import shuffle from "npm:lodash.shuffle";
 | 
					 | 
				
			||||||
import * as d3 from "npm:d3-color";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const PALETTES = new Map([
 | 
					 | 
				
			||||||
	[ "random RGB", palette_random ], 
 | 
					 | 
				
			||||||
	[ "grayscale", palette_grayscale ],
 | 
					 | 
				
			||||||
    [ "monochrome", palette_monochrome ],
 | 
					 | 
				
			||||||
    [ "one spot", palette_one_spot ],
 | 
					 | 
				
			||||||
    [ "triad", triad_saturated ],
 | 
					 | 
				
			||||||
    [ "triad pastel", triad_pastel ],
 | 
					 | 
				
			||||||
    [ "triad dusk", triad_dusk ],
 | 
					 | 
				
			||||||
  	[ "RGB", palette_rgb ],
 | 
					 | 
				
			||||||
  	[ "RBY", palette_rby ],
 | 
					 | 
				
			||||||
  	[ "CMY", palette_cmy ],
 | 
					 | 
				
			||||||
]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// two spot
 | 
					 | 
				
			||||||
 	// RGB
 | 
					 | 
				
			||||||
 	// CMY
 | 
					 | 
				
			||||||
 	// RYB
 | 
					 | 
				
			||||||
 	// triad - full saturation
 | 
					 | 
				
			||||||
 	// triad - pastel
 | 
					 | 
				
			||||||
 	// trial - dusk
 | 
					 | 
				
			||||||
 	// random HSV
 | 
					 | 
				
			||||||
 	// random RGB
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_random() {
 | 
					 | 
				
			||||||
	const u = random.uniform(0, 255);
 | 
					 | 
				
			||||||
	return [1,2,3].map((x)=> d3.rgb(u(), u(), u()));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_grayscale() {
 | 
					 | 
				
			||||||
	const u = random.uniform(0, 1);
 | 
					 | 
				
			||||||
	return [1,2,3].map((x)=> d3.hsl(0, 0, u()));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_monochrome() {
 | 
					 | 
				
			||||||
	const u = random.uniform(0, 1);
 | 
					 | 
				
			||||||
	const h = u() * 360;
 | 
					 | 
				
			||||||
	return [1,2,3].map((x)=> d3.hsl(h, u(), u()));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_one_spot() {
 | 
					 | 
				
			||||||
	const hue = random.uniform(0, 360);
 | 
					 | 
				
			||||||
	const cols = [ d3.color("white"), d3.color("black"), d3.hsl(hue(), 1, 0.5) ]
 | 
					 | 
				
			||||||
	return shuffle(cols);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function triad_saturated() {
 | 
					 | 
				
			||||||
	return triad(1, 0.5);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function triad_pastel() {
 | 
					 | 
				
			||||||
	return triad(0.6, 0.7);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function triad_dusk() {
 | 
					 | 
				
			||||||
	return triad(1, 0.25);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function triad(s, l) {
 | 
					 | 
				
			||||||
	const u = random.uniform(0, 360);
 | 
					 | 
				
			||||||
	const h1 = u();
 | 
					 | 
				
			||||||
	const h2 = (h1 + 120) % 360;
 | 
					 | 
				
			||||||
	const h3 = (h1 + 240) % 360;
 | 
					 | 
				
			||||||
	const cols = [ h1, h2, h3 ].map((h) => d3.hsl(h, s, l));
 | 
					 | 
				
			||||||
	return shuffle(cols); 
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_rgb() {
 | 
					 | 
				
			||||||
	const cols = [ d3.rgb(255, 0, 0), d3.rgb(0, 255, 0), d3.rgb(0, 0, 255) ];
 | 
					 | 
				
			||||||
	return shuffle(cols);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_rby() {
 | 
					 | 
				
			||||||
	const cols = [ d3.rgb(255, 0, 0), d3.rgb(255, 255, 0), d3.rgb(0, 0, 255) ];
 | 
					 | 
				
			||||||
	return shuffle(cols);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function palette_cmy() {
 | 
					 | 
				
			||||||
	const cols = [ d3.rgb(0, 255, 255), d3.rgb(255, 255, 0), d3.rgb(255, 0, 255) ];
 | 
					 | 
				
			||||||
	return shuffle(cols);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DotControls {
 | 
					class DotControls {
 | 
				
			||||||
@ -116,10 +32,6 @@ class DotControls {
 | 
				
			|||||||
		this.r.dispatchEvent(new Event("input"));		
 | 
							this.r.dispatchEvent(new Event("input"));		
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	random_colours() {
 | 
					 | 
				
			||||||
		this.set_colour(random_colour());
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	set_colour(c) {
 | 
						set_colour(c) {
 | 
				
			||||||
		this.fg.value = c;
 | 
							this.fg.value = c;
 | 
				
			||||||
		this.fg.dispatchEvent(new Event("input"));
 | 
							this.fg.dispatchEvent(new Event("input"));
 | 
				
			||||||
@ -128,4 +40,4 @@ class DotControls {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { DotControls, PALETTES };
 | 
					export { DotControls };
 | 
				
			||||||
@ -61,7 +61,6 @@ export function svg_to_string(svg) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function download_as_svg(svg) {
 | 
					export function download_as_svg(svg) {
 | 
				
			||||||
  console.log("HEY download_as_svg");
 | 
					 | 
				
			||||||
  const str = svg_to_string(svg);
 | 
					  const str = svg_to_string(svg);
 | 
				
			||||||
  return new Blob([str], {type: "image/svg+xml"})
 | 
					  return new Blob([str], {type: "image/svg+xml"})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										78
									
								
								src/components/palettes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/components/palettes.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					import * as d3 from "d3-color";
 | 
				
			||||||
 | 
					import shuffle from "lodash.shuffle";
 | 
				
			||||||
 | 
					import random from "random";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PALETTES = new Map([
 | 
				
			||||||
 | 
						[ "random RGB", palette_random ], 
 | 
				
			||||||
 | 
						[ "grayscale", palette_grayscale ],
 | 
				
			||||||
 | 
					    [ "monochrome", palette_monochrome ],
 | 
				
			||||||
 | 
					    [ "one spot", palette_one_spot ],
 | 
				
			||||||
 | 
					    [ "triad", triad_saturated ],
 | 
				
			||||||
 | 
					    [ "triad pastel", triad_pastel ],
 | 
				
			||||||
 | 
					    [ "triad dusk", triad_dusk ],
 | 
				
			||||||
 | 
					  	[ "RGB", palette_rgb ],
 | 
				
			||||||
 | 
					  	[ "RBY", palette_rby ],
 | 
				
			||||||
 | 
					  	[ "CMY", palette_cmy ],
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_random() {
 | 
				
			||||||
 | 
						const u = random.uniform(0, 255);
 | 
				
			||||||
 | 
						return [1,2,3].map((x)=> d3.rgb(u(), u(), u()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_grayscale() {
 | 
				
			||||||
 | 
						const u = random.uniform(0, 1);
 | 
				
			||||||
 | 
						return [1,2,3].map((x)=> d3.hsl(0, 0, u()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_monochrome() {
 | 
				
			||||||
 | 
						const u = random.uniform(0, 1);
 | 
				
			||||||
 | 
						const h = u() * 360;
 | 
				
			||||||
 | 
						return [1,2,3].map((x)=> d3.hsl(h, u(), u()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_one_spot() {
 | 
				
			||||||
 | 
						const hue = random.uniform(0, 360);
 | 
				
			||||||
 | 
						const cols = [ d3.color("white"), d3.color("black"), d3.hsl(hue(), 1, 0.5) ]
 | 
				
			||||||
 | 
						return shuffle(cols);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function triad_saturated() {
 | 
				
			||||||
 | 
						return triad(1, 0.5);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function triad_pastel() {
 | 
				
			||||||
 | 
						return triad(0.6, 0.7);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function triad_dusk() {
 | 
				
			||||||
 | 
						return triad(1, 0.25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function triad(s, l) {
 | 
				
			||||||
 | 
						const u = random.uniform(0, 360);
 | 
				
			||||||
 | 
						const h1 = u();
 | 
				
			||||||
 | 
						const h2 = (h1 + 120) % 360;
 | 
				
			||||||
 | 
						const h3 = (h1 + 240) % 360;
 | 
				
			||||||
 | 
						const cols = [ h1, h2, h3 ].map((h) => d3.hsl(h, s, l));
 | 
				
			||||||
 | 
						return shuffle(cols); 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_rgb() {
 | 
				
			||||||
 | 
						const cols = [ d3.rgb(255, 0, 0), d3.rgb(0, 255, 0), d3.rgb(0, 0, 255) ];
 | 
				
			||||||
 | 
						return shuffle(cols);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_rby() {
 | 
				
			||||||
 | 
						const cols = [ d3.rgb(255, 0, 0), d3.rgb(255, 255, 0), d3.rgb(0, 0, 255) ];
 | 
				
			||||||
 | 
						return shuffle(cols);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function palette_cmy() {
 | 
				
			||||||
 | 
						const cols = [ d3.rgb(0, 255, 255), d3.rgb(255, 255, 0), d3.rgb(255, 0, 255) ];
 | 
				
			||||||
 | 
						return shuffle(cols);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { PALETTES }
 | 
				
			||||||
@ -15,7 +15,8 @@ toc: false
 | 
				
			|||||||
```js
 | 
					```js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {RADIUS_OPTS, DotMaker} from './components/dots.js';
 | 
					import {RADIUS_OPTS, DotMaker} from './components/dots.js';
 | 
				
			||||||
import {PALETTES, DotControls} from './components/controls.js';
 | 
					import {DotControls} from './components/controls.js';
 | 
				
			||||||
 | 
					import {PALETTES} from './components/palettes.js';
 | 
				
			||||||
import {download, download_as_svg, download_as_png} from './components/download.js';
 | 
					import {download, download_as_svg, download_as_png} from './components/download.js';
 | 
				
			||||||
import random from "npm:random";
 | 
					import random from "npm:random";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user