init
14
jdenticon-js/.eslintrc.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 11,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
};
|
||||
3
jdenticon-js/.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Treat minifyed files as binary to ensure the integrity does not change
|
||||
dist/*.min.js binary
|
||||
143
jdenticon-js/.github/workflows/tests.js.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Tests
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
TAP_COLORS: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and run unit tests
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TAP_NO_ESM: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- name: Use Node.js v14
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 14.x
|
||||
- run: npm install
|
||||
|
||||
- name: Build Jdenticon
|
||||
run: npm run build
|
||||
|
||||
- name: TypeScript typings tests
|
||||
run: npm run test:types
|
||||
- name: Unit tests
|
||||
run: npm run test:unit
|
||||
|
||||
- name: Webpack 4 bundle test
|
||||
run: npm run test:webpack4
|
||||
- name: Webpack 5 bundle test
|
||||
run: npm run test:webpack5
|
||||
|
||||
- name: Rollup bundle test
|
||||
run: npm run test:rollup
|
||||
|
||||
- name: Node test (CommonJS)
|
||||
run: npm run test:node-cjs
|
||||
- name: Node test (ESM)
|
||||
run: npm run test:node-esm
|
||||
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: package
|
||||
path: ./test/node_modules/jdenticon
|
||||
|
||||
e2e:
|
||||
name: E2E tests (Node ${{ matrix.node }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node: [ '6.4', '8.x', '10.x', '12.x', '18.x', '20.x' ]
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
|
||||
- uses: actions/download-artifact@v4.1.7
|
||||
with:
|
||||
name: package
|
||||
path: test/node_modules/jdenticon
|
||||
|
||||
- name: Use Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
# Use an older tap version to ensure compatibility with the old Node version
|
||||
# bind-obj-methods broke old Node 6 support in 2.0.1
|
||||
- name: npm install (Node 6.4)
|
||||
if: ${{ matrix.node == '6.4' }}
|
||||
run: |
|
||||
npm install -g npm@6.14.17
|
||||
npm install tap@12.7.0 bind-obj-methods@2.0.0
|
||||
|
||||
- name: npm install (Node 8.x)
|
||||
if: ${{ matrix.node == '8.x' }}
|
||||
run: npm install tap@14.11.0
|
||||
|
||||
- name: npm install (Node 10+)
|
||||
if: ${{ matrix.node != '6.4' && matrix.node != '8.x' }}
|
||||
run: npm install
|
||||
|
||||
- name: Node test (CommonJS)
|
||||
run: npm run test:node-cjs
|
||||
|
||||
- name: Node test (ESM, Node 12+)
|
||||
if: ${{ matrix.node != '6.4' && matrix.node != '8.x' && matrix.node != '10.x' }}
|
||||
run: npm run test:node-esm
|
||||
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: e2e-${{ matrix.node }}
|
||||
path: ./test/e2e/node/expected
|
||||
|
||||
visual:
|
||||
name: Visual tests
|
||||
needs: build
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ 'macos-latest', 'windows-latest' ]
|
||||
env:
|
||||
ARTIFACTS_DIR: ./artifacts
|
||||
BROWSER_SCREENSHOT_DIR: ./artifacts/screenshots
|
||||
BROWSER_DIFF_DIR: ./artifacts/diffs
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.5
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version: 16.x
|
||||
- run: npm install
|
||||
|
||||
- uses: actions/download-artifact@v4.1.7
|
||||
with:
|
||||
name: package
|
||||
path: test/node_modules/jdenticon
|
||||
|
||||
- name: Run visual tests (Windows)
|
||||
if: ${{ startsWith(matrix.os, 'windows') }}
|
||||
run: |
|
||||
$env:PATH = "C:\SeleniumWebDrivers\IEDriver;$env:PATH"
|
||||
npm run test:browser-win
|
||||
- name: Run visual tests (macOS)
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
run: npm run test:browser-macos
|
||||
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: visual-${{ matrix.os }}
|
||||
path: ${{ env.ARTIFACTS_DIR }}
|
||||
|
||||
7
jdenticon-js/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
artifacts
|
||||
obj
|
||||
releases
|
||||
bower_components
|
||||
node_modules
|
||||
.vs
|
||||
.nyc_output
|
||||
21
jdenticon-js/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-2024 Daniel Mester Pirttijärvi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
72
jdenticon-js/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# [Jdenticon](https://jdenticon.com)
|
||||
JavaScript library for generating highly recognizable identicons using HTML5 canvas or SVG.
|
||||
|
||||

|
||||
|
||||
[](https://github.com/dmester/jdenticon/actions)
|
||||
[](https://www.npmjs.com/package/jdenticon)
|
||||
[](https://www.jsdelivr.com/package/npm/jdenticon)
|
||||
[](https://bundlephobia.com/result?p=jdenticon)
|
||||
[](https://github.com/dmester/jdenticon/blob/master/LICENSE)
|
||||
|
||||
## Live demo
|
||||
https://jdenticon.com
|
||||
|
||||
## Getting started
|
||||
Using Jdenticon is simple. Follow the steps below to integrate Jdenticon into your website.
|
||||
|
||||
### 1. Add identicon placeholders
|
||||
Jdenticon is able to render both raster and vector identicons. Raster icons are rendered
|
||||
slightly faster than vector icons, but vector icons scale better on high resolution screens.
|
||||
Add a canvas to render a raster icon, or an inline svg element to render a vector icon.
|
||||
|
||||
```HTML
|
||||
<!-- Vector icon -->
|
||||
<svg width="80" height="80" data-jdenticon-value="icon value"></svg>
|
||||
|
||||
<!-- OR -->
|
||||
|
||||
<!-- Raster icon -->
|
||||
<canvas width="80" height="80" data-jdenticon-value="icon value"></canvas>
|
||||
```
|
||||
|
||||
### 2. Add reference to Jdenticon
|
||||
Include the Jdenticon library somewhere on your page. You can either host it yourself or
|
||||
use it right off [jsDelivr](https://www.jsdelivr.com).
|
||||
|
||||
```HTML
|
||||
<!-- Using jsDelivr -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jdenticon@3.3.0/dist/jdenticon.min.js"
|
||||
integrity="sha384-LfouGM03m83ArVtne1JPk926e3SGD0Tz8XHtW2OKGsgeBU/UfR0Fa8eX+UlwSSAZ"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<!-- OR -->
|
||||
|
||||
<!-- Hosting it yourself -->
|
||||
<script src="-path-to-/jdenticon.min.js"></script>
|
||||
```
|
||||
That's it!
|
||||
|
||||
## Other resources
|
||||
### API documentation
|
||||
For more usage examples and API documentation, please see:
|
||||
|
||||
https://jdenticon.com
|
||||
|
||||
### Other platforms
|
||||
There are ports or bindings for Jdenticon available for the following platforms:
|
||||
|
||||
* [PHP](https://github.com/dmester/jdenticon-php/)
|
||||
* [React](https://www.npmjs.com/package/react-jdenticon)
|
||||
* [Angular](https://www.npmjs.com/package/ngx-jdenticon)
|
||||
* [.NET](https://github.com/dmester/jdenticon-net/)
|
||||
* [Rust](https://github.com/jay3332/rdenticon)
|
||||
* [Polymer](https://github.com/GeoloeG/identicon-element)
|
||||
* [Swift](https://github.com/aleph7/jdenticon-swift)
|
||||
* [Java](https://github.com/sunshower-io/sunshower-arcus/tree/master/arcus-identicon)
|
||||
* [Dart/Flutter](https://pub.dartlang.org/packages/jdenticon_dart)
|
||||
* [Kotlin](https://github.com/WycliffeAssociates/jdenticon-kotlin)
|
||||
|
||||
## License
|
||||
Jdenticon is available under the [MIT license](https://github.com/dmester/jdenticon/blob/master/LICENSE).
|
||||
177
jdenticon-js/bin/jdenticon.js
Normal file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs");
|
||||
const jdenticon = require("../dist/jdenticon-node");
|
||||
|
||||
|
||||
// Handle command
|
||||
|
||||
const parsedArgs = parseArgs(process.argv);
|
||||
|
||||
if (parsedArgs.help) {
|
||||
writeHelp();
|
||||
process.exit(0);
|
||||
|
||||
} else if (parsedArgs.version) {
|
||||
console.log(jdenticon.version);
|
||||
process.exit(0);
|
||||
|
||||
} else {
|
||||
const validatedArgs = validateArgs(parsedArgs);
|
||||
if (validatedArgs) {
|
||||
var output = validatedArgs.svg ?
|
||||
jdenticon.toSvg(validatedArgs.value, validatedArgs.size, validatedArgs.config) :
|
||||
jdenticon.toPng(validatedArgs.value, validatedArgs.size, validatedArgs.config);
|
||||
|
||||
if (validatedArgs.output) {
|
||||
fs.writeFileSync(validatedArgs.output, output);
|
||||
} else {
|
||||
process.stdout.write(output);
|
||||
}
|
||||
process.exit(0);
|
||||
|
||||
} else {
|
||||
writeHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Functions
|
||||
|
||||
function writeHelp() {
|
||||
console.log("Generates an identicon as a PNG or SVG file for a specified value.");
|
||||
console.log("");
|
||||
console.log("Usage: jdenticon <value> [-s <size>] [-o <filename>]");
|
||||
console.log("");
|
||||
console.log("Options:");
|
||||
console.log(" -s, --size <value> Icon size in pixels. (default: 100)");
|
||||
console.log(" -o, --output <path> Output file. (default: <stdout>)");
|
||||
console.log(" -f, --format <svg|png> Format of generated icon. Otherwise detected from output path. (default: png)");
|
||||
console.log(" -b, --back-color <value> Background color on format #rgb, #rgba, #rrggbb or #rrggbbaa. (default: transparent)");
|
||||
console.log(" -p, --padding <value> Padding in percent in range 0 to 0.5. (default: 0.08)");
|
||||
console.log(" -v, --version Gets the version of Jdenticon.");
|
||||
console.log(" -h, --help Show this help information.");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" jdenticon user127 -s 100 -o icon.png");
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
// Argument 1 is always node
|
||||
// Argument 2 is always jdenticon
|
||||
// Argument 3 and forward are actual arguments
|
||||
args = args.slice(2);
|
||||
|
||||
function consume(aliases, hasValue) {
|
||||
for (var argIndex = 0; argIndex < args.length; argIndex++) {
|
||||
var arg = args[argIndex];
|
||||
|
||||
for (var aliasIndex = 0; aliasIndex < aliases.length; aliasIndex++) {
|
||||
var alias = aliases[aliasIndex];
|
||||
|
||||
if (arg === alias) {
|
||||
var value;
|
||||
|
||||
if (hasValue) {
|
||||
if (argIndex + 1 < args.length) {
|
||||
value = args[argIndex + 1];
|
||||
} else {
|
||||
console.warn("WARN Missing value of argument " + alias);
|
||||
}
|
||||
} else {
|
||||
value = true;
|
||||
}
|
||||
|
||||
args.splice(argIndex, hasValue ? 2 : 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (arg.startsWith(alias) && arg[alias.length] === "=") {
|
||||
var value = arg.substr(alias.length + 1);
|
||||
if (!hasValue) {
|
||||
value = value !== "false";
|
||||
}
|
||||
args.splice(argIndex, 1);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (consume(["-h", "--help", "-?", "/?", "/h"], false)) {
|
||||
return {
|
||||
help: true
|
||||
};
|
||||
}
|
||||
|
||||
if (consume(["-v", "--version"], false)) {
|
||||
return {
|
||||
version: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
size: consume(["-s", "--size"], true),
|
||||
output: consume(["-o", "--output"], true),
|
||||
format: consume(["-f", "--format"], true),
|
||||
padding: consume(["-p", "--padding"], true),
|
||||
backColor: consume(["-b", "--back-color"], true),
|
||||
value: args
|
||||
};
|
||||
}
|
||||
|
||||
function validateArgs(args) {
|
||||
if (args.value.length) {
|
||||
|
||||
// Size
|
||||
var size = 100;
|
||||
if (args.size) {
|
||||
size = Number(args.size);
|
||||
if (!size || size < 1) {
|
||||
size = 100;
|
||||
console.warn("WARN Invalid size specified. Defaults to 100.");
|
||||
}
|
||||
}
|
||||
|
||||
// Padding
|
||||
var padding;
|
||||
if (args.padding != null) {
|
||||
padding = Number(args.padding);
|
||||
if (isNaN(padding) || padding < 0 || padding >= 0.5) {
|
||||
padding = 0.08;
|
||||
console.warn("WARN Invalid padding specified. Defaults to 0.08.");
|
||||
}
|
||||
}
|
||||
|
||||
// Background color
|
||||
var backColor;
|
||||
if (args.backColor != null) {
|
||||
backColor = args.backColor;
|
||||
if (!/^(#[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(backColor)) {
|
||||
backColor = undefined;
|
||||
console.warn("WARN Invalid background color specified. Defaults to transparent.");
|
||||
}
|
||||
}
|
||||
|
||||
// Format
|
||||
var generateSvg =
|
||||
args.format ? /^svg$/i.test(args.format) :
|
||||
args.output ? /\.svg$/i.test(args.output) :
|
||||
false;
|
||||
if (args.format != null && !/^(svg|png)$/i.test(args.format)) {
|
||||
console.warn("WARN Invalid format specified. Defaults to " + (generateSvg ? "svg" : "png") + ".");
|
||||
}
|
||||
|
||||
return {
|
||||
config: {
|
||||
padding: padding,
|
||||
backColor: backColor
|
||||
},
|
||||
output: args.output,
|
||||
size: size,
|
||||
svg: generateSvg,
|
||||
value: args.value.join("")
|
||||
};
|
||||
}
|
||||
}
|
||||
32
jdenticon-js/bower.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "Jdenticon",
|
||||
"authors": [
|
||||
"Daniel Mester Pirttijärvi"
|
||||
],
|
||||
"description": "Javascript identicon generator",
|
||||
"main": "dist/jdenticon.js",
|
||||
"keywords": [
|
||||
"javascript",
|
||||
"identicon",
|
||||
"avatar",
|
||||
"library"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://jdenticon.com/",
|
||||
"ignore": [
|
||||
".npmignore",
|
||||
".gitignore",
|
||||
".vs",
|
||||
"*.bat",
|
||||
"*.nuspec",
|
||||
"build",
|
||||
"gulpfile.js",
|
||||
"node_modules",
|
||||
"obj",
|
||||
"releases",
|
||||
"src",
|
||||
"template.*",
|
||||
"test",
|
||||
"utils"
|
||||
]
|
||||
}
|
||||
4
jdenticon-js/browser/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "../dist/jdenticon-module",
|
||||
"types": "../types/module.d.ts"
|
||||
}
|
||||
34
jdenticon-js/build/gulp/ast-transform-stream.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const { Transform } = require("stream");
|
||||
const { parse } = require("acorn");
|
||||
const { Replacement } = require("./replacement");
|
||||
|
||||
function astTransformStream(transformer) {
|
||||
return new Transform({
|
||||
objectMode: true,
|
||||
|
||||
transform(inputFile, _, fileDone) {
|
||||
const input = inputFile.contents.toString();
|
||||
|
||||
const comments = [];
|
||||
const ast = parse(input, {
|
||||
ecmaVersion: 10,
|
||||
sourceType: "module",
|
||||
onComment: comments,
|
||||
});
|
||||
|
||||
const replacement = new Replacement();
|
||||
transformer(replacement, ast, comments, input);
|
||||
const output = replacement.replace(input, inputFile.sourceMap);
|
||||
|
||||
inputFile.contents = Buffer.from(output.output);
|
||||
|
||||
if (inputFile.sourceMap) {
|
||||
inputFile.sourceMap = output.sourceMap;
|
||||
}
|
||||
|
||||
fileDone(null, inputFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = transformer => () => astTransformStream(transformer);
|
||||
4120
jdenticon-js/build/gulp/domprops.js
Normal file
186
jdenticon-js/build/gulp/pre-minify.js
Normal file
@@ -0,0 +1,186 @@
|
||||
const { Node } = require("acorn");
|
||||
const astTransformStream = require("./ast-transform-stream");
|
||||
const DOMPROPS = require("./domprops");
|
||||
const RESERVED_NAMES = require("./reserved-keywords");
|
||||
|
||||
const CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
|
||||
|
||||
function visit(node, visitor) {
|
||||
if (node instanceof Node) {
|
||||
visitor(node);
|
||||
}
|
||||
|
||||
for (const key in node) {
|
||||
const value = node[key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(subNode => visit(subNode, visitor));
|
||||
} else if (value instanceof Node) {
|
||||
visit(value, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateIdentifier(seed) {
|
||||
let identifier = "";
|
||||
|
||||
seed = Math.abs(Math.floor(seed));
|
||||
|
||||
do {
|
||||
const mod = seed % CHARACTERS.length;
|
||||
identifier += CHARACTERS[mod];
|
||||
seed = (seed / CHARACTERS.length) | 0;
|
||||
} while (seed);
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
function mangleProps(input, ast, replacement) {
|
||||
const identifierNodes = [];
|
||||
const longToShortName = new Map();
|
||||
|
||||
// Find identifiers
|
||||
visit(ast, node => {
|
||||
let identifier;
|
||||
|
||||
if (node.type === "MemberExpression" && !node.computed) {
|
||||
// Matches x.y
|
||||
// Not x["y"] (computed: true)
|
||||
identifier = node.property;
|
||||
} else if (node.type === "MethodDefinition") {
|
||||
// Matches x() { }
|
||||
identifier = node.key;
|
||||
} else if (node.type === "Property") {
|
||||
// Matches { x: y }
|
||||
// Not { "x": y }
|
||||
identifier = node.key;
|
||||
}
|
||||
|
||||
if (identifier && identifier.type === "Identifier") {
|
||||
identifierNodes.push(identifier);
|
||||
}
|
||||
});
|
||||
|
||||
// Collect usage statistics per name
|
||||
const usageMap = new Map();
|
||||
identifierNodes.forEach(node => {
|
||||
if (node.name && !RESERVED_NAMES.has(node.name) && !DOMPROPS.has(node.name)) {
|
||||
usageMap.set(node.name, (usageMap.get(node.name) || 0) + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by usage in descending order
|
||||
const usageStats = Array.from(usageMap).sort((a, b) => b[1] - a[1]);
|
||||
|
||||
// Allocate identifiers in order of usage statistics to ensure
|
||||
// frequently used symbols get as short identifiers as possible.
|
||||
let runningCounter = 0;
|
||||
usageStats.forEach(identifier => {
|
||||
const longName = identifier[0];
|
||||
|
||||
if (!longToShortName.has(longName)) {
|
||||
let shortName;
|
||||
|
||||
do {
|
||||
shortName = generateIdentifier(runningCounter++);
|
||||
} while (RESERVED_NAMES.has(shortName) || DOMPROPS.has(shortName));
|
||||
|
||||
longToShortName.set(longName, shortName);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate replacements
|
||||
identifierNodes.forEach(node => {
|
||||
const minifiedName = longToShortName.get(node.name);
|
||||
if (minifiedName) {
|
||||
replacement.addRange({
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
replacement: minifiedName + "/*" + node.name + "*/",
|
||||
name: node.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return replacement;
|
||||
}
|
||||
|
||||
function simplifyES5Class(input, ast, replacement) {
|
||||
const prototypeMemberExpressions = [];
|
||||
const duplicateNamedFunctions = [];
|
||||
|
||||
visit(ast, node => {
|
||||
if (node.type === "MemberExpression" &&
|
||||
!node.computed &&
|
||||
node.object.type === "Identifier" &&
|
||||
node.property.type === "Identifier" &&
|
||||
node.property.name === "prototype"
|
||||
) {
|
||||
// Matches: xxx.prototype
|
||||
prototypeMemberExpressions.push(node);
|
||||
|
||||
} else if (
|
||||
node.type === "VariableDeclaration" &&
|
||||
node.declarations.length === 1 &&
|
||||
node.declarations[0].init &&
|
||||
node.declarations[0].init.type === "FunctionExpression" &&
|
||||
node.declarations[0].init.id &&
|
||||
node.declarations[0].init.id.name === node.declarations[0].id.name
|
||||
) {
|
||||
// Matches: var xxx = function xxx ();
|
||||
duplicateNamedFunctions.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
duplicateNamedFunctions.forEach(duplicateNamedFunction => {
|
||||
const functionName = duplicateNamedFunction.declarations[0].init.id.name;
|
||||
|
||||
// Remove: var xxx =
|
||||
replacement.addRange({
|
||||
start: duplicateNamedFunction.start,
|
||||
end: duplicateNamedFunction.declarations[0].init.start,
|
||||
replacement: "",
|
||||
});
|
||||
|
||||
// Remove trailing semicolons
|
||||
let semicolons = 0;
|
||||
while (input[duplicateNamedFunction.end - semicolons - 1] === ";") semicolons++;
|
||||
|
||||
// Find prototype references
|
||||
const refs = prototypeMemberExpressions.filter(node => node.object.name === functionName);
|
||||
if (refs.length > 1) {
|
||||
|
||||
// Insert: var xx__prototype = xxx.prototype;
|
||||
replacement.addRange({
|
||||
start: duplicateNamedFunction.end - semicolons,
|
||||
end: duplicateNamedFunction.end,
|
||||
replacement: `\r\nvar ${functionName}__prototype = ${functionName}.prototype;`,
|
||||
});
|
||||
|
||||
// Replace references
|
||||
refs.forEach(ref => {
|
||||
replacement.addRange({
|
||||
start: ref.start,
|
||||
end: ref.end,
|
||||
replacement: `${functionName}__prototype`,
|
||||
});
|
||||
});
|
||||
} else if (semicolons) {
|
||||
replacement.addRange({
|
||||
start: duplicateNamedFunction.end - semicolons,
|
||||
end: duplicateNamedFunction.end,
|
||||
replacement: "",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function browserConstants(input, ast, replacement) {
|
||||
replacement.addText("Node.ELEMENT_NODE", "1");
|
||||
}
|
||||
|
||||
const MINIFIERS = [simplifyES5Class, mangleProps, browserConstants];
|
||||
|
||||
module.exports = astTransformStream(function (replacement, ast, comments, input) {
|
||||
MINIFIERS.forEach(minifier => minifier(input, ast, replacement));
|
||||
});
|
||||
|
||||
52
jdenticon-js/build/gulp/remove-jsdoc-imports.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const astTransformStream = require("./ast-transform-stream");
|
||||
|
||||
function removeJSDocImports(comments, replacement) {
|
||||
const REGEX = /[ \t]*\*[ \t]*@typedef\s+\{import.+\r?\n?|import\(.*?\)\.([a-zA-Z_]+)/g;
|
||||
const JSDOC_COMMENT_OFFSET = 2;
|
||||
|
||||
comments.forEach(comment => {
|
||||
if (comment.type === "Block" && comment.value[0] === "*") {
|
||||
// JSDoc comment
|
||||
const resultingComment = comment.value.replace(REGEX, (match, importName, matchIndex) => {
|
||||
matchIndex += comment.start + JSDOC_COMMENT_OFFSET;
|
||||
|
||||
if (importName) {
|
||||
// { import().xxx }
|
||||
replacement.addRange({
|
||||
start: matchIndex,
|
||||
end: matchIndex + match.length,
|
||||
replacement: importName,
|
||||
});
|
||||
|
||||
return importName;
|
||||
|
||||
} else {
|
||||
// @typedef
|
||||
replacement.addRange({
|
||||
start: matchIndex,
|
||||
end: matchIndex + match.length,
|
||||
replacement: "",
|
||||
});
|
||||
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
if (!/[^\s\*]/.test(resultingComment)) {
|
||||
// Empty comment left
|
||||
replacement.addRange({
|
||||
start: comment.start,
|
||||
end: comment.end,
|
||||
replacement: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = astTransformStream(function (replacement, ast, comments, input) {
|
||||
removeJSDocImports(comments, replacement);
|
||||
});
|
||||
|
||||
|
||||
|
||||
54
jdenticon-js/build/gulp/remove-mapped-source.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const { Transform } = require("stream");
|
||||
const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
|
||||
|
||||
function removeMappedSourceStream(...sourceNames) {
|
||||
const sourceNamesToRemove = new Set(sourceNames);
|
||||
|
||||
return new Transform({
|
||||
objectMode: true,
|
||||
|
||||
transform(inputFile, _, fileDone) {
|
||||
if (inputFile.sourceMap) {
|
||||
let consumer = inputFile.sourceMap;
|
||||
if (!(consumer instanceof SourceMapConsumer)) {
|
||||
consumer = new SourceMapConsumer(consumer);
|
||||
}
|
||||
|
||||
const generator = new SourceMapGenerator({
|
||||
file: consumer.file,
|
||||
sourceRoot: consumer.sourceRoot,
|
||||
});
|
||||
|
||||
consumer.sources.forEach(sourceFile => {
|
||||
const content = consumer.sourceContentFor(sourceFile);
|
||||
if (content != null && !sourceNamesToRemove.has(sourceFile)) {
|
||||
generator.setSourceContent(sourceFile, content);
|
||||
}
|
||||
});
|
||||
|
||||
consumer.eachMapping(mapping => {
|
||||
if (!sourceNamesToRemove.has(mapping.source)) {
|
||||
generator.addMapping({
|
||||
original: {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn,
|
||||
},
|
||||
generated: {
|
||||
line: mapping.generatedLine,
|
||||
column: mapping.generatedColumn,
|
||||
},
|
||||
source: mapping.source,
|
||||
name: mapping.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inputFile.sourceMap = generator.toJSON();
|
||||
}
|
||||
|
||||
fileDone(null, inputFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = removeMappedSourceStream;
|
||||
672
jdenticon-js/build/gulp/replacement.js
Normal file
@@ -0,0 +1,672 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
const { Transform } = require("stream");
|
||||
const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
|
||||
|
||||
/**
|
||||
* Finds substrings and replaces them with other strings, keeping any input source map up-to-date.
|
||||
*
|
||||
* @example
|
||||
* const replacement = new Replacement([
|
||||
* ["find this", "replace with this"],
|
||||
* [/find this/gi, "replace with this"]
|
||||
* ]);
|
||||
* replacement.replace(input, inputSourceMap);
|
||||
*
|
||||
* @example
|
||||
* const replacement = new Replacement("find this", "replace with this");
|
||||
* replacement.replace(input, inputSourceMap);
|
||||
*/
|
||||
class Replacement {
|
||||
constructor(...definition) {
|
||||
/**
|
||||
* @type {function(Array<OverwriteRange>, string): void}
|
||||
*/
|
||||
this._matchers = [];
|
||||
|
||||
/**
|
||||
* @type {Array<OverwriteRange>}
|
||||
*/
|
||||
this._ranges = [];
|
||||
|
||||
this.add(definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @returns {Array<OverwriteRange>}
|
||||
*/
|
||||
matchAll(input) {
|
||||
const ranges = [...this._ranges];
|
||||
this._matchers.forEach(matcher => matcher(ranges, input));
|
||||
|
||||
let lastReplacement;
|
||||
|
||||
return ranges
|
||||
.sort((a, b) => a.start - b.start)
|
||||
.filter(replacement => {
|
||||
if (!lastReplacement || lastReplacement.end <= replacement.start) {
|
||||
lastReplacement = replacement;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {SourceMap=} inputSourceMap
|
||||
* @returns {{ output: string, sourceMap: SourceMap }}
|
||||
*/
|
||||
replace(input, inputSourceMap) {
|
||||
const ranges = this.matchAll(input);
|
||||
const offset = new Offset();
|
||||
const reader = new InputReader(input);
|
||||
const sourceMap = new SourceMapSpooler(inputSourceMap);
|
||||
const output = [];
|
||||
|
||||
if (sourceMap.isEmpty()) {
|
||||
sourceMap.initEmpty(reader.lines);
|
||||
}
|
||||
|
||||
ranges.forEach((range, rangeIndex) => {
|
||||
output.push(reader.readTo(range.start));
|
||||
output.push(range.replacement);
|
||||
|
||||
const inputStart = reader.pos;
|
||||
|
||||
const replacedText = reader.readTo(range.end);
|
||||
if (replacedText === range.replacement) {
|
||||
return; // Nothing to do
|
||||
}
|
||||
|
||||
const inputEnd = reader.pos;
|
||||
|
||||
const replacementLines = range.replacement.split(/\n/g);
|
||||
const lineDifference = replacementLines.length + inputStart.line - inputEnd.line - 1;
|
||||
|
||||
const outputStart = {
|
||||
line: inputStart.line + offset.lineOffset,
|
||||
column: inputStart.column + offset.getColumnOffset(inputStart.line)
|
||||
};
|
||||
const outputEnd = {
|
||||
line: inputEnd.line + offset.lineOffset + lineDifference,
|
||||
column: replacementLines.length > 1 ?
|
||||
replacementLines[replacementLines.length - 1].length :
|
||||
inputStart.column + offset.getColumnOffset(inputStart.line) +
|
||||
range.replacement.length
|
||||
}
|
||||
|
||||
sourceMap.spoolTo(inputStart.line, inputStart.column, offset);
|
||||
|
||||
offset.lineOffset += lineDifference;
|
||||
offset.setColumnOffset(inputEnd.line, outputEnd.column - inputEnd.column);
|
||||
|
||||
if (range.name || replacementLines.length === 1 && range.replacement) {
|
||||
const mappingBeforeStart = sourceMap.lastMapping();
|
||||
const mappingAfterStart = sourceMap.nextMapping();
|
||||
|
||||
if (mappingAfterStart &&
|
||||
mappingAfterStart.generatedLine === inputStart.line &&
|
||||
mappingAfterStart.generatedColumn === inputStart.column
|
||||
) {
|
||||
sourceMap.addMapping({
|
||||
original: {
|
||||
line: mappingAfterStart.originalLine,
|
||||
column: mappingAfterStart.originalColumn,
|
||||
},
|
||||
generated: {
|
||||
line: outputStart.line,
|
||||
column: outputStart.column
|
||||
},
|
||||
source: mappingAfterStart.source,
|
||||
name: range.name,
|
||||
});
|
||||
|
||||
} else if (mappingBeforeStart && mappingBeforeStart.generatedLine === inputStart.line) {
|
||||
sourceMap.addMapping({
|
||||
original: {
|
||||
line: mappingBeforeStart.originalLine + inputStart.line - mappingBeforeStart.generatedLine,
|
||||
column: mappingBeforeStart.originalColumn + inputStart.column - mappingBeforeStart.generatedColumn,
|
||||
},
|
||||
generated: {
|
||||
line: outputStart.line,
|
||||
column: outputStart.column
|
||||
},
|
||||
source: mappingBeforeStart.source,
|
||||
name: range.name,
|
||||
});
|
||||
}
|
||||
|
||||
} else if (range.replacement) {
|
||||
// Map longer replacements to a virtual file defined in the source map
|
||||
const generatedSourceName = sourceMap.addSourceContent(replacedText, range.replacement);
|
||||
|
||||
for (var i = 0; i < replacementLines.length; i++) {
|
||||
// Don't map empty lines
|
||||
if (replacementLines[i]) {
|
||||
sourceMap.addMapping({
|
||||
original: {
|
||||
line: i + 1,
|
||||
column: 0,
|
||||
},
|
||||
generated: {
|
||||
line: outputStart.line + i,
|
||||
column: i ? 0 : outputStart.column,
|
||||
},
|
||||
source: generatedSourceName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceMap.skipTo(inputEnd.line, inputEnd.column, offset);
|
||||
|
||||
// Add a source map node directly after the replacement to terminate the replacement
|
||||
const mappingAfterEnd = sourceMap.nextMapping();
|
||||
const mappingBeforeEnd = sourceMap.lastMapping();
|
||||
|
||||
if (mappingAfterEnd &&
|
||||
mappingAfterEnd.generatedLine === inputEnd.line &&
|
||||
mappingAfterEnd.generatedColumn === inputEnd.column
|
||||
) {
|
||||
// No extra source map node needed when the replacement is directly followed by another node
|
||||
|
||||
} else if (rangeIndex + 1 < ranges.length && range.end === ranges[rangeIndex + 1].start) {
|
||||
// The next replacement range is adjacent to this one
|
||||
|
||||
} else if (reader.endOfLine()) {
|
||||
// End of line, no point in adding a following node
|
||||
|
||||
} else if (!mappingBeforeEnd || mappingBeforeEnd.generatedLine !== inputEnd.line) {
|
||||
// No applicable preceding node found
|
||||
|
||||
} else {
|
||||
sourceMap.addMapping({
|
||||
original: {
|
||||
line: mappingBeforeEnd.originalLine + inputEnd.line - mappingBeforeEnd.generatedLine,
|
||||
column: mappingBeforeEnd.originalColumn + inputEnd.column - mappingBeforeEnd.generatedColumn,
|
||||
},
|
||||
generated: {
|
||||
line: outputEnd.line,
|
||||
column: outputEnd.column
|
||||
},
|
||||
source: mappingBeforeEnd.source,
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Flush remaining input to output and source map
|
||||
output.push(reader.readToEnd());
|
||||
sourceMap.spoolToEnd(offset);
|
||||
|
||||
return {
|
||||
output: output.join(""),
|
||||
sourceMap: sourceMap.toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
add(value) {
|
||||
const target = this;
|
||||
|
||||
function addRecursive(innerValue) {
|
||||
if (innerValue != null) {
|
||||
if (Array.isArray(innerValue)) {
|
||||
const needle = innerValue[0];
|
||||
|
||||
if (typeof needle === "string") {
|
||||
target.addText(...innerValue);
|
||||
} else if (needle instanceof RegExp) {
|
||||
target.addRegExp(...innerValue);
|
||||
} else {
|
||||
innerValue.forEach(addRecursive);
|
||||
}
|
||||
|
||||
} else if (innerValue instanceof Replacement) {
|
||||
target._matchers.push(innerValue._matchers);
|
||||
target._ranges.push(innerValue._ranges);
|
||||
|
||||
} else if (typeof innerValue === "object") {
|
||||
target.addRange(innerValue);
|
||||
|
||||
} else {
|
||||
throw new Error("Unknown replacement argument specified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addRecursive(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RegExp} re
|
||||
* @param {string|function(string, ...):string} replacement
|
||||
* @param {{ name: string }=} rangeOpts
|
||||
*/
|
||||
addRegExp(re, replacement, rangeOpts) {
|
||||
const replacementFactory = this._createReplacementFactory(replacement);
|
||||
|
||||
this._matchers.push((ranges, input) => {
|
||||
const isGlobalRegExp = /g/.test(re.flags);
|
||||
|
||||
let match;
|
||||
let isFirstIteration = true;
|
||||
|
||||
while ((isFirstIteration || isGlobalRegExp) && (match = re.exec(input))) {
|
||||
ranges.push(new OverwriteRange({
|
||||
...rangeOpts,
|
||||
start: match.index,
|
||||
end: match.index + match[0].length,
|
||||
replacement: replacementFactory(match, match.index, input),
|
||||
}));
|
||||
isFirstIteration = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} needle
|
||||
* @param {string|function(string, ...):string} replacement
|
||||
* @param {{ name: string }=} rangeOpts
|
||||
*/
|
||||
addText(needle, replacement, rangeOpts) {
|
||||
const replacementFactory = this._createReplacementFactory(replacement);
|
||||
|
||||
this._matchers.push((ranges, input) => {
|
||||
let index = -needle.length;
|
||||
|
||||
while ((index = input.indexOf(needle, index + needle.length)) >= 0) {
|
||||
ranges.push(new OverwriteRange({
|
||||
...rangeOpts,
|
||||
start: index,
|
||||
end: index + needle.length,
|
||||
replacement: replacementFactory([needle], index, input),
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {OverwriteRange} range
|
||||
*/
|
||||
addRange(range) {
|
||||
this._ranges.push(new OverwriteRange(range));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|function(string, ...):string} replacement
|
||||
* @returns {function(Array<string>, number, string):string}
|
||||
*/
|
||||
_createReplacementFactory(replacement) {
|
||||
if (typeof replacement === "function") {
|
||||
return (match, index, input) => replacement(...match, index, input);
|
||||
}
|
||||
|
||||
if (replacement == null) {
|
||||
return () => "";
|
||||
}
|
||||
|
||||
replacement = replacement.toString();
|
||||
|
||||
if (replacement.indexOf("$") < 0) {
|
||||
return () => replacement;
|
||||
}
|
||||
|
||||
return (match, index, input) =>
|
||||
replacement.replace(/\$(\d+|[$&`'])/g, matchedPattern => {
|
||||
if (matchedPattern === "$$") {
|
||||
return "$";
|
||||
}
|
||||
if (matchedPattern === "$&") {
|
||||
return match[0];
|
||||
}
|
||||
if (matchedPattern === "$`") {
|
||||
return input.substring(0, index);
|
||||
}
|
||||
if (matchedPattern === "$'") {
|
||||
return input.substring(index + match[0].length);
|
||||
}
|
||||
|
||||
const matchArrayIndex = Number(matchedPattern.substring(1));
|
||||
return match[matchArrayIndex];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class InputReader {
|
||||
/**
|
||||
* @param {string} input
|
||||
*/
|
||||
constructor (input) {
|
||||
// Find index of all line breaks
|
||||
const lineBreakIndexes = [];
|
||||
let index = -1;
|
||||
while ((index = input.indexOf("\n", index + 1)) >= 0) {
|
||||
lineBreakIndexes.push(index);
|
||||
}
|
||||
|
||||
this._input = input;
|
||||
|
||||
this._inputCursorExclusive = 0;
|
||||
this._output = [];
|
||||
this._lineBreakIndexes = lineBreakIndexes;
|
||||
|
||||
/**
|
||||
* Number of lines in the input file.
|
||||
* @type {number}
|
||||
*/
|
||||
this.lines = this._lineBreakIndexes.length + 1;
|
||||
|
||||
/**
|
||||
* Position of the input cursor. Line number is one-based and column number is zero-based.
|
||||
* @type {{ line: number, column: number }}
|
||||
*/
|
||||
this.pos = { line: 1, column: 0 };
|
||||
}
|
||||
|
||||
readTo(exclusiveIndex) {
|
||||
let result = "";
|
||||
|
||||
if (this._inputCursorExclusive < exclusiveIndex) {
|
||||
result = this._input.substring(this._inputCursorExclusive, exclusiveIndex);
|
||||
this._inputCursorExclusive = exclusiveIndex;
|
||||
this._updatePos();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
readToEnd() {
|
||||
return this.readTo(this._input.length);
|
||||
}
|
||||
|
||||
endOfLine() {
|
||||
const nextChar = this._input[this._inputCursorExclusive];
|
||||
return !nextChar || nextChar === "\r" || nextChar === "\n";
|
||||
}
|
||||
|
||||
_updatePos() {
|
||||
let line = this.pos.line;
|
||||
while (
|
||||
line - 1 < this._lineBreakIndexes.length &&
|
||||
this._lineBreakIndexes[line - 1] < this._inputCursorExclusive
|
||||
) {
|
||||
line++;
|
||||
}
|
||||
|
||||
const lineStartIndex = this._lineBreakIndexes[line - 2];
|
||||
const column = this._inputCursorExclusive - (lineStartIndex || -1) - 1;
|
||||
this.pos = { line, column };
|
||||
}
|
||||
}
|
||||
|
||||
class SourceMapSpooler {
|
||||
/**
|
||||
* @param {SourceMap=} inputSourceMap
|
||||
*/
|
||||
constructor(inputSourceMap) {
|
||||
let generator;
|
||||
let file;
|
||||
let sources;
|
||||
let mappings = [];
|
||||
|
||||
if (inputSourceMap) {
|
||||
if (!(inputSourceMap instanceof SourceMapConsumer)) {
|
||||
inputSourceMap = new SourceMapConsumer(inputSourceMap);
|
||||
}
|
||||
|
||||
generator = new SourceMapGenerator({
|
||||
file: inputSourceMap.file,
|
||||
sourceRoot: inputSourceMap.sourceRoot,
|
||||
});
|
||||
|
||||
inputSourceMap.sources.forEach(function(sourceFile) {
|
||||
const content = inputSourceMap.sourceContentFor(sourceFile);
|
||||
if (content != null) {
|
||||
generator.setSourceContent(sourceFile, content);
|
||||
}
|
||||
});
|
||||
|
||||
inputSourceMap.eachMapping(mapping => {
|
||||
mappings.push(mapping);
|
||||
});
|
||||
|
||||
mappings.sort((a, b) => a.generatedLine == b.generatedLine
|
||||
? a.generatedColumn - b.generatedColumn : a.generatedLine - b.generatedLine);
|
||||
|
||||
file = inputSourceMap.file;
|
||||
sources = inputSourceMap.sources;
|
||||
|
||||
} else {
|
||||
generator = new SourceMapGenerator();
|
||||
file = "input";
|
||||
sources = [];
|
||||
}
|
||||
|
||||
this._generator = generator;
|
||||
this._sources = new Set(sources);
|
||||
this._file = file;
|
||||
|
||||
this._mappingsCursor = 0;
|
||||
this._mappings = mappings;
|
||||
|
||||
this._contents = new Map();
|
||||
}
|
||||
|
||||
lastMapping() {
|
||||
return this._mappings[this._mappingsCursor - 1];
|
||||
}
|
||||
|
||||
nextMapping() {
|
||||
return this._mappings[this._mappingsCursor];
|
||||
}
|
||||
|
||||
addMapping(mapping) {
|
||||
this._generator.addMapping(mapping);
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this._mappings.length === 0;
|
||||
}
|
||||
|
||||
initEmpty(lines) {
|
||||
this._mappings = [];
|
||||
|
||||
for (var i = 0; i < lines; i++) {
|
||||
this._mappings.push({
|
||||
originalLine: i + 1,
|
||||
originalColumn: 0,
|
||||
generatedLine: i + 1,
|
||||
generatedColumn: 0,
|
||||
source: this._file
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addSourceContent(replacedText, content) {
|
||||
let sourceName = this._contents.get(content);
|
||||
|
||||
if (!sourceName) {
|
||||
const PREFIX = "replacement/";
|
||||
|
||||
let sourceNameWithoutNumber = PREFIX;
|
||||
sourceName = sourceNameWithoutNumber + "1";
|
||||
|
||||
if (replacedText.length > 0 && replacedText.length < 25) {
|
||||
replacedText = replacedText
|
||||
.replace(/^[^0-9a-z-_]+|[^0-9a-z-_]+$/ig, "")
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^0-9a-z-_]/ig, "");
|
||||
|
||||
if (replacedText) {
|
||||
sourceNameWithoutNumber = PREFIX + replacedText + "-";
|
||||
sourceName = PREFIX + replacedText;
|
||||
}
|
||||
}
|
||||
|
||||
let counter = 2;
|
||||
while (this._sources.has(sourceName)) {
|
||||
sourceName = sourceNameWithoutNumber + counter++;
|
||||
}
|
||||
|
||||
this._sources.add(sourceName);
|
||||
this._contents.set(content, sourceName);
|
||||
this._generator.setSourceContent(sourceName, content);
|
||||
}
|
||||
|
||||
return sourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies source map info from input to output up to but not including the specified position.
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
* @param {Offset} offset
|
||||
*/
|
||||
spoolTo(line, column, offset) {
|
||||
this._consume(line, column, offset, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies remaining source map info from input to output.
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
* @param {Offset} offset
|
||||
*/
|
||||
spoolToEnd(offset) {
|
||||
this._consume(null, null, offset, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards source map info from input up to but not including the specified position.
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
* @param {Offset} offset
|
||||
*/
|
||||
skipTo(line, column, offset) {
|
||||
this._consume(line, column, offset, false);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this._generator.toJSON();
|
||||
}
|
||||
|
||||
_consume(line, column, offset, keep) {
|
||||
let mapping;
|
||||
|
||||
while (
|
||||
(mapping = this._mappings[this._mappingsCursor]) &&
|
||||
(
|
||||
line == null ||
|
||||
mapping.generatedLine < line ||
|
||||
mapping.generatedLine == line && mapping.generatedColumn < column
|
||||
)
|
||||
) {
|
||||
if (keep) {
|
||||
this._generator.addMapping({
|
||||
original: {
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn,
|
||||
},
|
||||
generated: {
|
||||
line: mapping.generatedLine + offset.lineOffset,
|
||||
column: mapping.generatedColumn + offset.getColumnOffset(mapping.generatedLine),
|
||||
},
|
||||
source: mapping.source,
|
||||
name: mapping.name,
|
||||
});
|
||||
}
|
||||
|
||||
this._mappingsCursor++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Offset {
|
||||
constructor() {
|
||||
this.lineOffset = 0;
|
||||
this._columnOffset = 0;
|
||||
this._columnOffsetForLine = 0;
|
||||
}
|
||||
|
||||
setColumnOffset(lineNumber, columnOffset) {
|
||||
this._columnOffsetForLine = lineNumber;
|
||||
this._columnOffset = columnOffset;
|
||||
}
|
||||
|
||||
getColumnOffset(lineNumber) {
|
||||
return this._columnOffsetForLine === lineNumber ?
|
||||
this._columnOffset : 0;
|
||||
}
|
||||
}
|
||||
|
||||
class OverwriteRange {
|
||||
constructor(options) {
|
||||
if (!isFinite(options.start)) {
|
||||
throw new Error("A replacement start index is required.");
|
||||
}
|
||||
if (!isFinite(options.end)) {
|
||||
throw new Error("A replacement end index is required.");
|
||||
}
|
||||
if (options.end < options.start) {
|
||||
throw new Error("Replacement end index cannot precede its start index.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive start index.
|
||||
* @type {number}
|
||||
*/
|
||||
this.start = options.start;
|
||||
|
||||
/**
|
||||
* Exclusive start index.
|
||||
* @type {number}
|
||||
*/
|
||||
this.end = options.end;
|
||||
|
||||
/**
|
||||
* The replacement interval will be replaced with this string.
|
||||
* @type string
|
||||
*/
|
||||
this.replacement = "" + options.replacement;
|
||||
|
||||
/**
|
||||
* Optional name that will be mapped in the source map.
|
||||
* @type string
|
||||
*/
|
||||
this.name = options.name;
|
||||
}
|
||||
}
|
||||
|
||||
function gulp(replacements) {
|
||||
if (typeof replacements === "string" || replacements instanceof RegExp) {
|
||||
replacements = Array.from(arguments);
|
||||
}
|
||||
|
||||
const replacer = new Replacement(replacements);
|
||||
|
||||
return new Transform({
|
||||
objectMode: true,
|
||||
|
||||
transform(inputFile, _, fileDone) {
|
||||
const input = inputFile.contents.toString();
|
||||
|
||||
const output = replacer.replace(input, inputFile.sourceMap);
|
||||
|
||||
inputFile.contents = Buffer.from(output.output);
|
||||
|
||||
if (inputFile.sourceMap) {
|
||||
inputFile.sourceMap = output.sourceMap;
|
||||
}
|
||||
|
||||
fileDone(null, inputFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { Replacement, gulp };
|
||||
70
jdenticon-js/build/gulp/reserved-keywords.js
Normal file
@@ -0,0 +1,70 @@
|
||||
module.exports = new Set([
|
||||
"async",
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"class",
|
||||
"const",
|
||||
"continue",
|
||||
"debugger",
|
||||
"default",
|
||||
"delete",
|
||||
"do",
|
||||
"else",
|
||||
"export",
|
||||
"extends",
|
||||
"finally",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"import",
|
||||
"in",
|
||||
"of",
|
||||
"instanceof",
|
||||
"new",
|
||||
"return",
|
||||
"super",
|
||||
"switch",
|
||||
"this",
|
||||
"throw",
|
||||
"try",
|
||||
"typeof",
|
||||
"var",
|
||||
"void",
|
||||
"while",
|
||||
"with",
|
||||
"yield",
|
||||
"enum",
|
||||
"implements",
|
||||
"interface",
|
||||
"let",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"static",
|
||||
"await",
|
||||
"abstract",
|
||||
"boolean",
|
||||
"byte",
|
||||
"char",
|
||||
"double",
|
||||
"final",
|
||||
"float",
|
||||
"goto",
|
||||
"int",
|
||||
"long",
|
||||
"native",
|
||||
"short",
|
||||
"synchronized",
|
||||
"throws",
|
||||
"transient",
|
||||
"volatile",
|
||||
"arguments",
|
||||
"get",
|
||||
"set",
|
||||
"null",
|
||||
"undefined",
|
||||
"exports",
|
||||
"module",
|
||||
]);
|
||||
56
jdenticon-js/build/gulp/rollup.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
const { rollup } = require("rollup");
|
||||
const Vinyl = require("vinyl");
|
||||
const applySourceMap = require("vinyl-sourcemaps-apply");
|
||||
const { Transform } = require("stream");
|
||||
|
||||
function rollupStream(options) {
|
||||
return new Transform({
|
||||
objectMode: true,
|
||||
|
||||
transform(inputFile, _, fileDone) {
|
||||
const inputOptions = {
|
||||
onwarn: warn => console.warn(warn.toString()),
|
||||
...options,
|
||||
input: inputFile.path,
|
||||
};
|
||||
delete inputOptions.output;
|
||||
|
||||
rollup(inputOptions).then(bundle => {
|
||||
return bundle.generate({
|
||||
...options.output,
|
||||
sourcemap: !!inputFile.sourceMap
|
||||
});
|
||||
|
||||
}).then(outputs => {
|
||||
|
||||
for (const output of outputs.output) {
|
||||
if (output.type === "chunk") {
|
||||
const outputFile = new Vinyl({
|
||||
cwd: inputFile.cwd,
|
||||
base: inputFile.base,
|
||||
path: inputFile.path,
|
||||
contents: Buffer.from(output.code),
|
||||
});
|
||||
|
||||
if (inputFile.sourceMap) {
|
||||
applySourceMap(outputFile, output.map);
|
||||
}
|
||||
|
||||
this.push(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
fileDone();
|
||||
|
||||
}, err => fileDone(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = rollupStream;
|
||||
26
jdenticon-js/build/gulp/wrap-template.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const fs = require("fs");
|
||||
const replace = require("./replacement").gulp;
|
||||
|
||||
function wrapTemplate(templatePath, variables) {
|
||||
let template = fs.readFileSync(templatePath).toString();
|
||||
|
||||
if (variables) {
|
||||
variables.forEach(variable => {
|
||||
template = template.replace(variable[0], variable[1]);
|
||||
});
|
||||
}
|
||||
|
||||
template = template.split(/\/\*content\*\//);
|
||||
|
||||
const replacements = [];
|
||||
if (template[0]) {
|
||||
replacements.push([/^/, template[0]]);
|
||||
}
|
||||
if (template[1]) {
|
||||
replacements.push([/$/, template[1]]);
|
||||
}
|
||||
|
||||
return replace(replacements);
|
||||
}
|
||||
|
||||
module.exports = wrapTemplate;
|
||||
27
jdenticon-js/build/jdenticon.nuspec
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Jdenticon</id>
|
||||
<version>#version#</version>
|
||||
<title>Jdenticon JS</title>
|
||||
<authors>Daniel Mester Pirttijärvi</authors>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>http://jdenticon.com/</projectUrl>
|
||||
<iconUrl>http://jdenticon.com/hosted/nuget-logo-js.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>JavaScript library generating identicons using SVG graphics or HTML5 canvas.
|
||||
|
||||
If you intend to generate icons server-side, consider using any of the .NET packages instead:
|
||||
* Jdenticon-net
|
||||
* Jdenticon.AspNetCore
|
||||
* Jdenticon.AspNet.Mvc
|
||||
* Jdenticon.AspNet.WebApi
|
||||
* Jdenticon.AspNet.WebForms</description>
|
||||
<tags>JavaScript identicon avatar library</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="obj\output\jdenticon-#version#.min.js" target="Content\Scripts\jdenticon-#version#.min.js" />
|
||||
<file src="obj\output\jdenticon-#version#.js" target="Content\Scripts\jdenticon-#version#.js" />
|
||||
<file src="obj\output\readme.txt" target="Content\Scripts\jdenticon.readme.txt" />
|
||||
</files>
|
||||
</package>
|
||||
BIN
jdenticon-js/build/nuget/NuGet.exe
Normal file
70
jdenticon-js/build/nuget/license.txt
Normal file
@@ -0,0 +1,70 @@
|
||||
Apache License 2.0 (Apache)
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
2. You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
2
jdenticon-js/build/template-min.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Jdenticon #version# | jdenticon.com | MIT licensed | (c) 2014-#year# Daniel Mester Pirttijärvi
|
||||
/*content*/
|
||||
10
jdenticon-js/build/template-module.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Jdenticon #version#
|
||||
* http://jdenticon.com
|
||||
*
|
||||
* Built: #date#
|
||||
*
|
||||
* #license#
|
||||
*/
|
||||
|
||||
/*content*/
|
||||
27
jdenticon-js/build/template-umd.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Jdenticon #version#
|
||||
* http://jdenticon.com
|
||||
*
|
||||
* Built: #date#
|
||||
*
|
||||
* #license#
|
||||
*/
|
||||
|
||||
(function (umdGlobal, factory) {
|
||||
var jdenticon = factory(umdGlobal);
|
||||
|
||||
// Node.js
|
||||
if (typeof module !== "undefined" && "exports" in module) {
|
||||
module["exports"] = jdenticon;
|
||||
}
|
||||
// RequireJS
|
||||
else if (typeof define === "function" && define["amd"]) {
|
||||
define([], function () { return jdenticon; });
|
||||
}
|
||||
// No module loader
|
||||
else {
|
||||
umdGlobal["jdenticon"] = jdenticon;
|
||||
}
|
||||
})(typeof self !== "undefined" ? self : this, function (umdGlobal) {
|
||||
/*content*/
|
||||
});
|
||||
67
jdenticon-js/dist/README.md
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# What file should I use?
|
||||
|
||||
## Overview
|
||||
|
||||
| Platform | Bundle | File name |
|
||||
|----------|------------------|----------------------|
|
||||
| Browser | Standalone (UMD) | jdenticon.js |
|
||||
| | | jdenticon.min.js |
|
||||
| | ES module | jdenticon-module.mjs |
|
||||
| | CommonJS module | jdenticon-module.js |
|
||||
| Node.js | ES module | jdenticon-node.mjs |
|
||||
| | CommonJS module | jdenticon-node.js |
|
||||
|
||||
## Node vs browser
|
||||
There are separate bundles for Node.js and browsers. The Node.js bundles contain support for generating PNG icons, while the browser bundles have support for updating DOM elements. It is important that the right bundle is used, since a web application bundle will be significally larger if the Node bundle is imported instead of the browser bundle.
|
||||
|
||||
## Don't address `dist/*` directly
|
||||
In first hand, don't import a specific file from the `dist` folder. Instead import the Jdenticon package and let the package decide what file to be imported. If your bundler does not pick the right file, and you cannot configure it to do so, there are explicit exports that you can use to force it to use the correct bundle:
|
||||
|
||||
| Platform | Export | Example |
|
||||
|----------|----------------------|----------------------------------------------|
|
||||
| Browser | jdenticon/browser | `import { toSvg } from "jdenticon/browser";` |
|
||||
| Node.js | jdenticon/node | `import { toSvg } from "jdenticon/node";` |
|
||||
| UMD | jdenticon/standalone | `import "jdenticon/standalone";` |
|
||||
|
||||
Jdenticon has multiple public entry points:
|
||||
|
||||
### ES module
|
||||
|
||||
For browsers `jdenticon-module.mjs` is imported and in Node.js environments `jdenticon-node.mjs` is imported. This is the preferred way of using Jdenticon since your bundler will most likely be able to eliminiate code from Jdenticon not used in your application (a.k.a. tree-shaking).
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
import { toSvg } from "jdenticon";
|
||||
|
||||
console.log(toSvg("my value", 100));
|
||||
```
|
||||
|
||||
### CommonJS module
|
||||
|
||||
If Jdenticon is imported on platforms not supporting ES modules, `jdenticon-module.js` is imported for browser environments and `jdenticon-node.js` in Node.js environments.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const { toSvg } = require("jdenticon");
|
||||
console.log(toSvg("my value", 100));
|
||||
// or
|
||||
const jdenticon = require("jdenticon");
|
||||
console.log(jdenticon.toSvg("my value", 100));
|
||||
```
|
||||
|
||||
### Standalone browser package
|
||||
|
||||
This package will render icons automatically at startup and also provides a legacy jQuery plugin, if jQuery is loaded before Jdenticon.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
import "jdenticon/standalone";
|
||||
|
||||
// or
|
||||
|
||||
import { toSvg } from "jdenticon/standalone";
|
||||
console.log(toSvg("my value", 100));
|
||||
```
|
||||
1405
jdenticon-js/dist/jdenticon-module.js
vendored
Normal file
1
jdenticon-js/dist/jdenticon-module.js.map
vendored
Normal file
1399
jdenticon-js/dist/jdenticon-module.mjs
vendored
Normal file
1
jdenticon-js/dist/jdenticon-module.mjs.map
vendored
Normal file
1276
jdenticon-js/dist/jdenticon-node.js
vendored
Normal file
1
jdenticon-js/dist/jdenticon-node.js.map
vendored
Normal file
1240
jdenticon-js/dist/jdenticon-node.mjs
vendored
Normal file
1
jdenticon-js/dist/jdenticon-node.mjs.map
vendored
Normal file
1507
jdenticon-js/dist/jdenticon.js
vendored
Normal file
3
jdenticon-js/dist/jdenticon.min.js
vendored
Normal file
1
jdenticon-js/dist/jdenticon.min.js.map
vendored
Normal file
298
jdenticon-js/gulpfile.js
Normal file
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const del = require("del");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
const { exec } = require("child_process");
|
||||
const { promisify } = require("util");
|
||||
const pack = require("./package.json");
|
||||
|
||||
// Gulp dependencies
|
||||
const gulp = require("gulp");
|
||||
const rename = require("gulp-rename");
|
||||
const terser = require("gulp-terser");
|
||||
const zip = require("gulp-zip");
|
||||
const replace = require("./build/gulp/replacement").gulp;
|
||||
const wrapTemplate = require("./build/gulp/wrap-template");
|
||||
const buble = require("gulp-buble");
|
||||
const sourcemaps = require("gulp-sourcemaps");
|
||||
const preMinify = require("./build/gulp/pre-minify");
|
||||
const removeJsDocImports = require("./build/gulp/remove-jsdoc-imports");
|
||||
const removeMappedSource = require("./build/gulp/remove-mapped-source");
|
||||
|
||||
// Rollup dependencies
|
||||
const rollup = require("./build/gulp/rollup");
|
||||
const commonjs = require( "@rollup/plugin-commonjs");
|
||||
const stripBanner = require("rollup-plugin-strip-banner");
|
||||
const alias = require("@rollup/plugin-alias");
|
||||
|
||||
// Constants
|
||||
const LICENSE = fs.readFileSync("./LICENSE").toString();
|
||||
const VARIABLES = [
|
||||
[/#version#/g, pack.version],
|
||||
[/#year#/g, new Date().getFullYear()],
|
||||
[/#date#/g, new Date().toISOString()],
|
||||
|
||||
// Keep line prefix, e.g. " * " for license banners in JavaScript.
|
||||
[/(.*)#license#/gm, "$1" + LICENSE.trim().replace(/\n/g, "\n$1")],
|
||||
];
|
||||
|
||||
function umdSrc() {
|
||||
return gulp.src("./src/browser-umd.js")
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(rollup({
|
||||
output: { format: "cjs" },
|
||||
plugins: [
|
||||
stripBanner(),
|
||||
alias({
|
||||
entries: [
|
||||
{ find: /^(.*[\/\\])global$/, replacement: "$1global.umd" },
|
||||
]
|
||||
}),
|
||||
],
|
||||
}))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "notmapped"; path.extname = ".js" }))
|
||||
|
||||
.pipe(buble())
|
||||
.pipe(preMinify())
|
||||
.pipe(removeJsDocImports())
|
||||
|
||||
// The UMD template expects a factory function body, so replace export with a return for the factory function.
|
||||
.pipe(replace("module.exports = ", "return "))
|
||||
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(wrapTemplate("./build/template-umd.js", VARIABLES));
|
||||
}
|
||||
|
||||
gulp.task("clean", function () {
|
||||
return del(["./~jdenticon.nuspec", "./obj/output"]);
|
||||
});
|
||||
|
||||
gulp.task("build-umd", function () {
|
||||
return umdSrc()
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon"; path.extname = ".js" }))
|
||||
.pipe(gulp.dest("dist"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-" + pack.version; path.extname = ".js" }))
|
||||
.pipe(gulp.dest("obj/output"));
|
||||
});
|
||||
|
||||
gulp.task("build-umd-min", function () {
|
||||
return umdSrc()
|
||||
.pipe(terser())
|
||||
.pipe(wrapTemplate("./build/template-min.js", VARIABLES))
|
||||
|
||||
.pipe(removeMappedSource("notmapped.js"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon"; path.extname = ".min.js" }))
|
||||
.pipe(sourcemaps.write(".", { includeContent: true }))
|
||||
.pipe(gulp.dest("dist"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-" + pack.version; path.extname = ".min.js" }))
|
||||
.pipe(sourcemaps.write(".", { includeContent: true }))
|
||||
.pipe(gulp.dest("obj/output"));
|
||||
});
|
||||
|
||||
gulp.task("build-cjs", function () {
|
||||
return gulp.src("./src/browser-cjs.js")
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(rollup({
|
||||
output: { format: "cjs" },
|
||||
plugins: [ stripBanner() ],
|
||||
}))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "notmapped"; path.extname = ".js" }))
|
||||
.pipe(buble())
|
||||
.pipe(preMinify())
|
||||
.pipe(removeJsDocImports())
|
||||
|
||||
// Replace variables
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(wrapTemplate("./build/template-module.js", VARIABLES))
|
||||
|
||||
.pipe(removeMappedSource("notmapped.js"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-module"; path.extname = ".js" }))
|
||||
.pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-module.js.map\n"))
|
||||
.pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
|
||||
.pipe(gulp.dest("dist"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-module-" + pack.version; path.extname = ".js" }))
|
||||
.pipe(gulp.dest("obj/output"))
|
||||
});
|
||||
|
||||
gulp.task("build-esm", function () {
|
||||
return gulp.src("./src/browser-esm.js")
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(rollup({
|
||||
output: { format: "esm" },
|
||||
plugins: [ stripBanner() ],
|
||||
}))
|
||||
|
||||
.pipe(preMinify())
|
||||
.pipe(removeJsDocImports())
|
||||
|
||||
// Replace variables
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(wrapTemplate("./build/template-module.js", VARIABLES))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-module"; path.extname = ".mjs" }))
|
||||
.pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-module.mjs.map\n"))
|
||||
.pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
|
||||
.pipe(gulp.dest("dist"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-module-" + pack.version; path.extname = ".mjs" }))
|
||||
.pipe(gulp.dest("obj/output"))
|
||||
});
|
||||
|
||||
gulp.task("build-node-cjs", function () {
|
||||
return gulp.src("./src/node-cjs.js")
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(rollup({
|
||||
external: [ "canvas-renderer" ],
|
||||
plugins: [ stripBanner(), commonjs() ],
|
||||
output: { format: "cjs" },
|
||||
}))
|
||||
|
||||
.pipe(removeJsDocImports())
|
||||
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(wrapTemplate("./build/template-module.js", VARIABLES))
|
||||
|
||||
.pipe(rename(path => { path.basename = "jdenticon-node"; path.extname = ".js" }))
|
||||
.pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-node.js.map\n"))
|
||||
.pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
|
||||
.pipe(gulp.dest("./dist"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-node-" + pack.version; path.extname = ".js" }))
|
||||
.pipe(gulp.dest("./obj/output"));
|
||||
});
|
||||
|
||||
gulp.task("build-node-esm", function () {
|
||||
return gulp.src("./src/node-esm.js")
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(rollup({
|
||||
external: [ "canvas-renderer" ],
|
||||
plugins: [ stripBanner(), commonjs() ],
|
||||
output: { format: "esm" },
|
||||
}))
|
||||
|
||||
.pipe(removeJsDocImports())
|
||||
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(wrapTemplate("./build/template-module.js", VARIABLES))
|
||||
|
||||
.pipe(rename(path => { path.basename = "jdenticon-node"; path.extname = ".mjs" }))
|
||||
.pipe(replace(/[\r\n]*$/, "\n//# sourceMappingURL=jdenticon-node.mjs.map\n"))
|
||||
.pipe(sourcemaps.write("./", { includeContent: true, addComment: false }))
|
||||
|
||||
.pipe(gulp.dest("./dist"))
|
||||
|
||||
.pipe(rename(function (path) { path.basename = "jdenticon-node-" + pack.version; path.extname = ".mjs" }))
|
||||
.pipe(gulp.dest("./obj/output"));
|
||||
});
|
||||
|
||||
gulp.task("update-license-year", function () {
|
||||
return gulp.src("./LICENSE")
|
||||
.pipe(replace(/\(c\) 2014-\d+/, "(c) 2014-" + new Date().getFullYear()))
|
||||
.pipe(gulp.dest("./"))
|
||||
});
|
||||
|
||||
gulp.task("update-readme", function () {
|
||||
return gulp.src("./README.md")
|
||||
.pipe(replace([
|
||||
[/@\d{1,2}\.\d{1,3}\.\d{1,3}/, "@" + pack.version],
|
||||
[/(?<=integrity=\"([^-]+)-).*?(?=\")/, (match, algorithm) => {
|
||||
const min = fs.readFileSync("./dist/jdenticon.min.js");
|
||||
return crypto.createHash(algorithm).update(min).digest("base64");
|
||||
}],
|
||||
]))
|
||||
.pipe(gulp.dest("./"))
|
||||
});
|
||||
|
||||
gulp.task("install-jdenticon-test", function () {
|
||||
const globs = pack.files
|
||||
.map(file => {
|
||||
const isDirectory =
|
||||
!/\*/.test(file) &&
|
||||
fs.existsSync(file) &&
|
||||
fs.lstatSync(file).isDirectory();
|
||||
return isDirectory ? path.join(file, "**") : file;
|
||||
});
|
||||
|
||||
// Simulate an installed Jdenticon package. Cannot use actual npm command, since it won't install Jdenticon in a
|
||||
// subfolder to the Jdenticon source package itself.
|
||||
return gulp.src(["./package.json", ...globs], { base: "./" })
|
||||
.pipe(gulp.dest("./test/node_modules/jdenticon"));
|
||||
});
|
||||
|
||||
gulp.task("build", gulp.series("clean", gulp.parallel(
|
||||
"build-umd", "build-umd-min",
|
||||
"build-esm", "build-cjs",
|
||||
"build-node-cjs", "build-node-esm",
|
||||
), "install-jdenticon-test"));
|
||||
|
||||
gulp.task("clean-tests", function () {
|
||||
return del(["./obj/test/unit/**"]);
|
||||
});
|
||||
|
||||
gulp.task("build-unit-tests-js", function () {
|
||||
return gulp.src("./test/unit/*.js", { base: "./" })
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(rollup({
|
||||
external: [ "canvas-renderer", "fs", "tap" ],
|
||||
plugins: [ commonjs() ],
|
||||
output: { format: "cjs" },
|
||||
}))
|
||||
.pipe(sourcemaps.write("./"))
|
||||
.pipe(gulp.dest("./obj"))
|
||||
});
|
||||
|
||||
gulp.task("build-unit-tests", gulp.series("clean-tests", "build-unit-tests-js"));
|
||||
|
||||
gulp.task("prepare-release", function () {
|
||||
return gulp.src(["./LICENSE", "./README.md"])
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(rename(function (path) { path.extname = ".txt" }))
|
||||
.pipe(gulp.dest("obj/output"));
|
||||
});
|
||||
|
||||
gulp.task("prepare-nuget", function () {
|
||||
return gulp.src(["./build/jdenticon.nuspec"])
|
||||
.pipe(replace(VARIABLES))
|
||||
.pipe(rename(function (path) { path.basename = "~" + path.basename }))
|
||||
.pipe(gulp.dest("./"));
|
||||
});
|
||||
|
||||
gulp.task("nuget", async function () {
|
||||
var command = "\"./build/nuget/nuget.exe\" pack ~jdenticon.nuspec -OutputDirectory releases";
|
||||
|
||||
if (process.platform !== "win32") {
|
||||
command = "mono " + command;
|
||||
}
|
||||
|
||||
await promisify(exec)(command);
|
||||
|
||||
await del(["./~jdenticon.nuspec"]);
|
||||
});
|
||||
|
||||
gulp.task("create-package", function () {
|
||||
return gulp.src(["./obj/output/*"])
|
||||
.pipe(zip("jdenticon-" + pack.version + ".zip"))
|
||||
.pipe(gulp.dest("releases"));
|
||||
});
|
||||
|
||||
gulp.task("release", gulp.series(
|
||||
"build",
|
||||
"update-license-year", "update-readme",
|
||||
"prepare-release",
|
||||
"create-package",
|
||||
"prepare-nuget", "nuget",
|
||||
));
|
||||
4
jdenticon-js/node/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "../dist/jdenticon-node",
|
||||
"types": "../types/module.d.ts"
|
||||
}
|
||||
15357
jdenticon-js/package-lock.json
generated
Normal file
126
jdenticon-js/package.json
Normal file
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"name": "jdenticon",
|
||||
"version": "3.3.0",
|
||||
"description": "Javascript identicon generator",
|
||||
"main": "dist/jdenticon-node.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/module.d.ts",
|
||||
"node": {
|
||||
"require": "./dist/jdenticon-node.js",
|
||||
"import": "./dist/jdenticon-node.mjs"
|
||||
},
|
||||
"browser": {
|
||||
"require": "./dist/jdenticon-module.js",
|
||||
"import": "./dist/jdenticon-module.mjs"
|
||||
},
|
||||
"default": "./dist/jdenticon-node.js"
|
||||
},
|
||||
"./browser": {
|
||||
"types": "./types/module.d.ts",
|
||||
"import": "./dist/jdenticon-module.mjs",
|
||||
"default": "./dist/jdenticon-module.js"
|
||||
},
|
||||
"./node": {
|
||||
"types": "./types/module.d.ts",
|
||||
"import": "./dist/jdenticon-node.mjs",
|
||||
"default": "./dist/jdenticon-node.js"
|
||||
},
|
||||
"./standalone": {
|
||||
"types": "./types/umd.d.ts",
|
||||
"default": "./dist/jdenticon.min.js"
|
||||
}
|
||||
},
|
||||
"browser": "dist/jdenticon-module",
|
||||
"jsdelivr": "dist/jdenticon.min.js",
|
||||
"unpkg": "dist/jdenticon.min.js",
|
||||
"types": "types/module.d.ts",
|
||||
"bin": {
|
||||
"jdenticon": "bin/jdenticon.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvas-renderer": "~2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^3.1.9",
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@types/jquery": "^3.5.14",
|
||||
"@types/node14": "npm:@types/node@14",
|
||||
"@types/node16": "npm:@types/node@16",
|
||||
"acorn": "^8.8.0",
|
||||
"blink-diff": "^1.0.13",
|
||||
"buble": "^0.20.0",
|
||||
"del": "^6.1.1",
|
||||
"eslint": "^8.21.0",
|
||||
"express": "^4.18.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-buble": "^0.9.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-terser": "^2.1.0",
|
||||
"gulp-zip": "^5.1.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"pngjs": "^6.0.0",
|
||||
"rollup": "^2.77.2",
|
||||
"rollup-plugin-strip-banner": "^2.0.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"selenium-webdriver": "^4.20.0",
|
||||
"source-map-loader": "^1.1.3",
|
||||
"tap": "^16.3.0",
|
||||
"typescript3": "npm:typescript@^3.2.4",
|
||||
"typescript4": "npm:typescript@^4.7.4",
|
||||
"webpack4": "npm:webpack@4",
|
||||
"webpack5": "npm:webpack@5"
|
||||
},
|
||||
"tap": {
|
||||
"check-coverage": false
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run lint && npm run tsc3 -- -p src/tsconfig.json && gulp build",
|
||||
"release": "gulp release",
|
||||
"lint": "eslint ./src/**/*.js",
|
||||
"test:unit": "gulp build-unit-tests && tap obj/test/unit/*.js",
|
||||
"test:types": "npm run test:types:browser && npm run test:types:node14 && npm run test:types:node16 && npm run test:types:umd",
|
||||
"test:types:browser": "npm run tsc3 -- -p ./test/types/module-browser/tsconfig.json && npm run tsc4 -- -p ./test/types/module-browser/tsconfig.json --moduleResolution node16",
|
||||
"test:types:node14": "npm run tsc3 -- -p ./test/types/module-node14/tsconfig.json",
|
||||
"test:types:node16": "npm run tsc4 -- -p ./test/types/module-node16/tsconfig.json && npm run tsc4 -- -p ./test/types/module-node16/tsconfig.json --moduleResolution node16",
|
||||
"test:types:umd": "npm run tsc3 -- -p ./test/types/umd/tsconfig.json && npm run tsc4 -- -p ./test/types/umd/tsconfig.json --moduleResolution node16",
|
||||
"test:browser-win": "tap ./test/e2e/browser/test.js --test-arg=win --test-arg=chrome,firefox,edge,ie11,ie10,ie9 --timeout=300",
|
||||
"test:browser-macos": "tap ./test/e2e/browser/test.js --test-arg=macos --test-arg=chrome,firefox,safari --timeout=300",
|
||||
"test:node-cjs": "tap ./test/e2e/node/test.js",
|
||||
"test:node-esm": "tap ./test/e2e/node/test.mjs",
|
||||
"test:webpack4": "cd ./test/e2e/webpack && node runner.js webpack4 && tap ./app.bundle.js --no-coverage",
|
||||
"test:webpack5": "cd ./test/e2e/webpack && node runner.js webpack5 && tap ./app.bundle.js --no-coverage",
|
||||
"test:rollup": "cd ./test/e2e/rollup && rollup --config && tap ./app.bundle.js --no-coverage",
|
||||
"tsc3": "node ./node_modules/typescript3/bin/tsc",
|
||||
"tsc4": "node ./node_modules/typescript4/bin/tsc"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"dist/",
|
||||
"standalone/",
|
||||
"browser/",
|
||||
"node/",
|
||||
"types/*.d.ts"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dmester/jdenticon"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/dmester/jdenticon/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
"identicon",
|
||||
"jdenticon",
|
||||
"avatar"
|
||||
],
|
||||
"author": "Daniel Mester Pirttijärvi",
|
||||
"license": "MIT",
|
||||
"homepage": "https://jdenticon.com/"
|
||||
}
|
||||
2
jdenticon-js/src/apis/configure.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export { configure } from "../common/configuration";
|
||||
28
jdenticon-js/src/apis/drawIcon.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { iconGenerator } from "../renderer/iconGenerator";
|
||||
import { isValidHash, computeHash } from "../common/hashUtils";
|
||||
import { CanvasRenderer } from "../renderer/canvas/canvasRenderer";
|
||||
import { IS_RENDERED_PROPERTY } from "../common/dom";
|
||||
|
||||
/**
|
||||
* Draws an identicon to a context.
|
||||
* @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
|
||||
* @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
|
||||
* @param {number} size - Icon size in pixels.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
|
||||
* global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
*/
|
||||
export function drawIcon(ctx, hashOrValue, size, config) {
|
||||
if (!ctx) {
|
||||
throw new Error("No canvas specified.");
|
||||
}
|
||||
|
||||
iconGenerator(new CanvasRenderer(ctx, size),
|
||||
isValidHash(hashOrValue) || computeHash(hashOrValue),
|
||||
config);
|
||||
|
||||
const canvas = ctx.canvas;
|
||||
if (canvas) {
|
||||
canvas[IS_RENDERED_PROPERTY] = true;
|
||||
}
|
||||
}
|
||||
18
jdenticon-js/src/apis/jquery.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { update } from "./update";
|
||||
|
||||
/**
|
||||
* Renders an identicon for all matching supported elements.
|
||||
*
|
||||
* @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not
|
||||
* specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be
|
||||
* evaluated.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global
|
||||
* configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
*/
|
||||
export function jdenticonJqueryPlugin(hashOrValue, config) {
|
||||
this["each"](function (index, el) {
|
||||
update(el, hashOrValue, config);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
24
jdenticon-js/src/apis/toPng.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import canvasRenderer from "canvas-renderer";
|
||||
import { iconGenerator } from "../renderer/iconGenerator";
|
||||
import { isValidHash, computeHash } from "../common/hashUtils";
|
||||
import { CanvasRenderer } from "../renderer/canvas/canvasRenderer";
|
||||
|
||||
/**
|
||||
* Draws an identicon as PNG.
|
||||
* @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
|
||||
* @param {number} size - Icon size in pixels.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
|
||||
* global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
* @returns {Buffer} PNG data
|
||||
*/
|
||||
export function toPng(hashOrValue, size, config) {
|
||||
const canvas = canvasRenderer.createCanvas(size, size);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
iconGenerator(new CanvasRenderer(ctx, size),
|
||||
isValidHash(hashOrValue) || computeHash(hashOrValue),
|
||||
config);
|
||||
|
||||
return canvas.toPng({ "Software": "Jdenticon" });
|
||||
}
|
||||
21
jdenticon-js/src/apis/toSvg.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { iconGenerator } from "../renderer/iconGenerator";
|
||||
import { isValidHash, computeHash } from "../common/hashUtils";
|
||||
import { SvgRenderer } from "../renderer/svg/svgRenderer";
|
||||
import { SvgWriter } from "../renderer/svg/svgWriter";
|
||||
|
||||
/**
|
||||
* Draws an identicon as an SVG string.
|
||||
* @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
|
||||
* @param {number} size - Icon size in pixels.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
|
||||
* global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
* @returns {string} SVG string
|
||||
*/
|
||||
export function toSvg(hashOrValue, size, config) {
|
||||
const writer = new SvgWriter(size);
|
||||
iconGenerator(new SvgRenderer(writer),
|
||||
isValidHash(hashOrValue) || computeHash(hashOrValue),
|
||||
config);
|
||||
return writer.toString();
|
||||
}
|
||||
149
jdenticon-js/src/apis/update.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { iconGenerator } from "../renderer/iconGenerator";
|
||||
import { isValidHash, computeHash } from "../common/hashUtils";
|
||||
import { ATTRIBUTES, ICON_SELECTOR, IS_RENDERED_PROPERTY, documentQuerySelectorAll } from "../common/dom";
|
||||
import { SvgRenderer } from "../renderer/svg/svgRenderer";
|
||||
import { SvgElement } from "../renderer/svg/svgElement";
|
||||
import { CanvasRenderer } from "../renderer/canvas/canvasRenderer";
|
||||
import { ICON_TYPE_CANVAS, ICON_TYPE_SVG, getIdenticonType } from "../common/dom";
|
||||
|
||||
|
||||
/**
|
||||
* Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute.
|
||||
*/
|
||||
export function updateAll() {
|
||||
if (documentQuerySelectorAll) {
|
||||
update(ICON_SELECTOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already
|
||||
* been rendered.
|
||||
*/
|
||||
export function updateAllConditional() {
|
||||
if (documentQuerySelectorAll) {
|
||||
/** @type {NodeListOf<HTMLElement>} */
|
||||
const elements = documentQuerySelectorAll(ICON_SELECTOR);
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const el = elements[i];
|
||||
if (!el[IS_RENDERED_PROPERTY]) {
|
||||
update(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the identicon in the specified `<canvas>` or `<svg>` elements.
|
||||
* @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
|
||||
* `<svg>` or `<canvas>`, or a CSS selector to such an element.
|
||||
* @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
|
||||
* `data-jdenticon-value` attribute will be evaluated.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
|
||||
* global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
*/
|
||||
export function update(el, hashOrValue, config) {
|
||||
renderDomElement(el, hashOrValue, config, function (el, iconType) {
|
||||
if (iconType) {
|
||||
return iconType == ICON_TYPE_SVG ?
|
||||
new SvgRenderer(new SvgElement(el)) :
|
||||
new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the identicon in the specified `<canvas>` elements.
|
||||
* @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
|
||||
* `<canvas>`, or a CSS selector to such an element.
|
||||
* @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
|
||||
* `data-jdenticon-value` attribute will be evaluated.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
|
||||
* global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
*/
|
||||
export function updateCanvas(el, hashOrValue, config) {
|
||||
renderDomElement(el, hashOrValue, config, function (el, iconType) {
|
||||
if (iconType == ICON_TYPE_CANVAS) {
|
||||
return new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the identicon in the specified `<svg>` elements.
|
||||
* @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
|
||||
* `<svg>`, or a CSS selector to such an element.
|
||||
* @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
|
||||
* `data-jdenticon-value` attribute will be evaluated.
|
||||
* @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
|
||||
* global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
|
||||
* specified in place of a configuration object.
|
||||
*/
|
||||
export function updateSvg(el, hashOrValue, config) {
|
||||
renderDomElement(el, hashOrValue, config, function (el, iconType) {
|
||||
if (iconType == ICON_TYPE_SVG) {
|
||||
return new SvgRenderer(new SvgElement(el));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the identicon in the specified canvas or svg elements.
|
||||
* @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
|
||||
* `<svg>` or `<canvas>`, or a CSS selector to such an element.
|
||||
* @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
|
||||
* `data-jdenticon-value` attribute will be evaluated.
|
||||
* @param {Object|number|undefined} config
|
||||
* @param {function(Element,number):import("../renderer/renderer").Renderer} rendererFactory - Factory function for creating an icon renderer.
|
||||
*/
|
||||
function renderDomElement(el, hashOrValue, config, rendererFactory) {
|
||||
if (typeof el === "string") {
|
||||
if (documentQuerySelectorAll) {
|
||||
const elements = documentQuerySelectorAll(el);
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
renderDomElement(elements[i], hashOrValue, config, rendererFactory);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash selection. The result from getValidHash or computeHash is
|
||||
// accepted as a valid hash.
|
||||
const hash =
|
||||
// 1. Explicit valid hash
|
||||
isValidHash(hashOrValue) ||
|
||||
|
||||
// 2. Explicit value (`!= null` catches both null and undefined)
|
||||
hashOrValue != null && computeHash(hashOrValue) ||
|
||||
|
||||
// 3. `data-jdenticon-hash` attribute
|
||||
isValidHash(el.getAttribute(ATTRIBUTES.HASH)) ||
|
||||
|
||||
// 4. `data-jdenticon-value` attribute.
|
||||
// We want to treat an empty attribute as an empty value.
|
||||
// Some browsers return empty string even if the attribute
|
||||
// is not specified, so use hasAttribute to determine if
|
||||
// the attribute is specified.
|
||||
el.hasAttribute(ATTRIBUTES.VALUE) && computeHash(el.getAttribute(ATTRIBUTES.VALUE));
|
||||
|
||||
if (!hash) {
|
||||
// No hash specified. Don't render an icon.
|
||||
return;
|
||||
}
|
||||
|
||||
const renderer = rendererFactory(el, getIdenticonType(el));
|
||||
if (renderer) {
|
||||
// Draw icon
|
||||
iconGenerator(renderer, hash, config);
|
||||
el[IS_RENDERED_PROPERTY] = true;
|
||||
}
|
||||
}
|
||||
39
jdenticon-js/src/browser-cjs.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
// This file is compiled to dist/jdenticon-module.js
|
||||
|
||||
import { defineConfigProperty } from "./common/configuration";
|
||||
import { configure } from "./apis/configure";
|
||||
import { drawIcon } from "./apis/drawIcon";
|
||||
import { toSvg } from "./apis/toSvg";
|
||||
import { update, updateAll, updateCanvas, updateSvg } from "./apis/update";
|
||||
|
||||
const jdenticon = updateAll;
|
||||
|
||||
defineConfigProperty(jdenticon);
|
||||
|
||||
// Export public API
|
||||
jdenticon["configure"] = configure;
|
||||
jdenticon["drawIcon"] = drawIcon;
|
||||
jdenticon["toSvg"] = toSvg;
|
||||
jdenticon["update"] = update;
|
||||
jdenticon["updateCanvas"] = updateCanvas;
|
||||
jdenticon["updateSvg"] = updateSvg;
|
||||
|
||||
/**
|
||||
* Specifies the version of the Jdenticon package in use.
|
||||
* @type {string}
|
||||
*/
|
||||
jdenticon["version"] = "#version#";
|
||||
|
||||
/**
|
||||
* Specifies which bundle of Jdenticon that is used.
|
||||
* @type {string}
|
||||
*/
|
||||
jdenticon["bundle"] = "browser-cjs";
|
||||
|
||||
module.exports = jdenticon;
|
||||
24
jdenticon-js/src/browser-esm.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
// This file is compiled to dist/jdenticon-module.mjs
|
||||
|
||||
export { configure } from "./apis/configure";
|
||||
export { drawIcon } from "./apis/drawIcon";
|
||||
export { toSvg } from "./apis/toSvg";
|
||||
export { update, updateCanvas, updateSvg } from "./apis/update";
|
||||
|
||||
/**
|
||||
* Specifies the version of the Jdenticon package in use.
|
||||
* @type {string}
|
||||
*/
|
||||
export const version = "#version#";
|
||||
|
||||
/**
|
||||
* Specifies which bundle of Jdenticon that is used.
|
||||
* @type {string}
|
||||
*/
|
||||
export const bundle = "browser-esm";
|
||||
71
jdenticon-js/src/browser-umd.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js
|
||||
|
||||
import { CONFIG_PROPERTIES, defineConfigProperty } from "./common/configuration";
|
||||
import { observer } from "./common/observer";
|
||||
import { configure } from "./apis/configure";
|
||||
import { drawIcon } from "./apis/drawIcon";
|
||||
import { toSvg } from "./apis/toSvg";
|
||||
import { update, updateAll, updateAllConditional } from "./apis/update";
|
||||
import { jdenticonJqueryPlugin } from "./apis/jquery";
|
||||
import { GLOBAL } from "./common/global";
|
||||
import { whenDocumentIsReady } from "./common/dom";
|
||||
|
||||
const jdenticon = updateAll;
|
||||
|
||||
defineConfigProperty(jdenticon);
|
||||
|
||||
// Export public API
|
||||
jdenticon["configure"] = configure;
|
||||
jdenticon["drawIcon"] = drawIcon;
|
||||
jdenticon["toSvg"] = toSvg;
|
||||
jdenticon["update"] = update;
|
||||
jdenticon["updateCanvas"] = update;
|
||||
jdenticon["updateSvg"] = update;
|
||||
|
||||
/**
|
||||
* Specifies the version of the Jdenticon package in use.
|
||||
* @type {string}
|
||||
*/
|
||||
jdenticon["version"] = "#version#";
|
||||
|
||||
/**
|
||||
* Specifies which bundle of Jdenticon that is used.
|
||||
* @type {string}
|
||||
*/
|
||||
jdenticon["bundle"] = "browser-umd";
|
||||
|
||||
// Basic jQuery plugin
|
||||
const jQuery = GLOBAL["jQuery"];
|
||||
if (jQuery) {
|
||||
jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called once upon page load.
|
||||
*/
|
||||
function jdenticonStartup() {
|
||||
const replaceMode = (
|
||||
jdenticon[CONFIG_PROPERTIES.MODULE] ||
|
||||
GLOBAL[CONFIG_PROPERTIES.GLOBAL] ||
|
||||
{ }
|
||||
)["replaceMode"];
|
||||
|
||||
if (replaceMode != "never") {
|
||||
updateAllConditional();
|
||||
|
||||
if (replaceMode == "observe") {
|
||||
observer(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule to render all identicons on the page once it has been loaded.
|
||||
whenDocumentIsReady(jdenticonStartup);
|
||||
|
||||
module.exports = jdenticon;
|
||||
152
jdenticon-js/src/common/configuration.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { parseColor } from "../renderer/color";
|
||||
import { GLOBAL } from "./global";
|
||||
|
||||
/**
|
||||
* @typedef {Object} ParsedConfiguration
|
||||
* @property {number} colorSaturation
|
||||
* @property {number} grayscaleSaturation
|
||||
* @property {string} backColor
|
||||
* @property {number} iconPadding
|
||||
* @property {function(number):number} hue
|
||||
* @property {function(number):number} colorLightness
|
||||
* @property {function(number):number} grayscaleLightness
|
||||
*/
|
||||
|
||||
export const CONFIG_PROPERTIES = {
|
||||
GLOBAL: "jdenticon_config",
|
||||
MODULE: "config",
|
||||
};
|
||||
|
||||
var rootConfigurationHolder = {};
|
||||
|
||||
/**
|
||||
* Defines the deprecated `config` property on the root Jdenticon object. When the property is set a warning is
|
||||
* printed in the console. To minimize bundle size, this is only used in Node bundles.
|
||||
* @param {!Object} rootObject
|
||||
*/
|
||||
export function defineConfigPropertyWithWarn(rootObject) {
|
||||
Object.defineProperty(rootObject, CONFIG_PROPERTIES.MODULE, {
|
||||
configurable: true,
|
||||
get: () => rootConfigurationHolder[CONFIG_PROPERTIES.MODULE],
|
||||
set: newConfiguration => {
|
||||
rootConfigurationHolder[CONFIG_PROPERTIES.MODULE] = newConfiguration;
|
||||
console.warn("jdenticon.config is deprecated. Use jdenticon.configure() instead.");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
|
||||
* when it is being used.
|
||||
* @param {!Object} rootObject
|
||||
*/
|
||||
export function defineConfigProperty(rootObject) {
|
||||
rootConfigurationHolder = rootObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new icon style configuration. The new configuration is not merged with the previous one. *
|
||||
* @param {Object} newConfiguration - New configuration object.
|
||||
*/
|
||||
export function configure(newConfiguration) {
|
||||
if (arguments.length) {
|
||||
rootConfigurationHolder[CONFIG_PROPERTIES.MODULE] = newConfiguration;
|
||||
}
|
||||
return rootConfigurationHolder[CONFIG_PROPERTIES.MODULE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the normalized current Jdenticon color configuration. Missing fields have default values.
|
||||
* @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
|
||||
* local configuration overrides the global configuration in it entirety. This parameter can for backward
|
||||
* compatibility also contain a padding value. A padding value only overrides the global padding, not the
|
||||
* entire global configuration.
|
||||
* @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
|
||||
* explicitly to the API method.
|
||||
* @returns {ParsedConfiguration}
|
||||
*/
|
||||
export function getConfiguration(paddingOrLocalConfig, defaultPadding) {
|
||||
const configObject =
|
||||
typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
|
||||
rootConfigurationHolder[CONFIG_PROPERTIES.MODULE] ||
|
||||
GLOBAL[CONFIG_PROPERTIES.GLOBAL] ||
|
||||
{ },
|
||||
|
||||
lightnessConfig = configObject["lightness"] || { },
|
||||
|
||||
// In versions < 2.1.0 there was no grayscale saturation -
|
||||
// saturation was the color saturation.
|
||||
saturation = configObject["saturation"] || { },
|
||||
colorSaturation = "color" in saturation ? saturation["color"] : saturation,
|
||||
grayscaleSaturation = saturation["grayscale"],
|
||||
|
||||
backColor = configObject["backColor"],
|
||||
padding = configObject["padding"];
|
||||
|
||||
/**
|
||||
* Creates a lightness range.
|
||||
*/
|
||||
function lightness(configName, defaultRange) {
|
||||
let range = lightnessConfig[configName];
|
||||
|
||||
// Check if the lightness range is an array-like object. This way we ensure the
|
||||
// array contain two values at the same time.
|
||||
if (!(range && range.length > 1)) {
|
||||
range = defaultRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a lightness relative the specified value in the specified lightness range.
|
||||
*/
|
||||
return function (value) {
|
||||
value = range[0] + value * (range[1] - range[0]);
|
||||
return value < 0 ? 0 : value > 1 ? 1 : value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a hue allowed by the configured hue restriction,
|
||||
* provided the originally computed hue.
|
||||
*/
|
||||
function hueFunction(originalHue) {
|
||||
const hueConfig = configObject["hues"];
|
||||
let hue;
|
||||
|
||||
// Check if 'hues' is an array-like object. This way we also ensure that
|
||||
// the array is not empty, which would mean no hue restriction.
|
||||
if (hueConfig && hueConfig.length > 0) {
|
||||
// originalHue is in the range [0, 1]
|
||||
// Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
|
||||
hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
|
||||
}
|
||||
|
||||
return typeof hue == "number" ?
|
||||
|
||||
// A hue was specified. We need to convert the hue from
|
||||
// degrees on any turn - e.g. 746° is a perfectly valid hue -
|
||||
// to turns in the range [0, 1).
|
||||
((((hue / 360) % 1) + 1) % 1) :
|
||||
|
||||
// No hue configured => use original hue
|
||||
originalHue;
|
||||
}
|
||||
|
||||
return {
|
||||
hue: hueFunction,
|
||||
colorSaturation: typeof colorSaturation == "number" ? colorSaturation : 0.5,
|
||||
grayscaleSaturation: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
|
||||
colorLightness: lightness("color", [0.4, 0.8]),
|
||||
grayscaleLightness: lightness("grayscale", [0.3, 0.9]),
|
||||
backColor: parseColor(backColor),
|
||||
iconPadding:
|
||||
typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
|
||||
typeof padding == "number" ? padding :
|
||||
defaultPadding
|
||||
}
|
||||
}
|
||||
56
jdenticon-js/src/common/dom.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
export const ICON_TYPE_SVG = 1;
|
||||
|
||||
export const ICON_TYPE_CANVAS = 2;
|
||||
|
||||
export const ATTRIBUTES = {
|
||||
HASH: "data-jdenticon-hash",
|
||||
VALUE: "data-jdenticon-value"
|
||||
};
|
||||
|
||||
export const IS_RENDERED_PROPERTY = "jdenticonRendered";
|
||||
|
||||
export const ICON_SELECTOR = "[" + ATTRIBUTES.HASH +"],[" + ATTRIBUTES.VALUE +"]";
|
||||
|
||||
export const documentQuerySelectorAll = /** @type {!Function} */ (
|
||||
typeof document !== "undefined" && document.querySelectorAll.bind(document));
|
||||
|
||||
export function getIdenticonType(el) {
|
||||
if (el) {
|
||||
const tagName = el["tagName"];
|
||||
|
||||
if (/^svg$/i.test(tagName)) {
|
||||
return ICON_TYPE_SVG;
|
||||
}
|
||||
|
||||
if (/^canvas$/i.test(tagName) && "getContext" in el) {
|
||||
return ICON_TYPE_CANVAS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function whenDocumentIsReady(/** @type {Function} */ callback) {
|
||||
function loadedHandler() {
|
||||
document.removeEventListener("DOMContentLoaded", loadedHandler);
|
||||
window.removeEventListener("load", loadedHandler);
|
||||
setTimeout(callback, 0); // Give scripts a chance to run
|
||||
}
|
||||
|
||||
if (typeof document !== "undefined" &&
|
||||
typeof window !== "undefined" &&
|
||||
typeof setTimeout !== "undefined"
|
||||
) {
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", loadedHandler);
|
||||
window.addEventListener("load", loadedHandler);
|
||||
} else {
|
||||
// Document already loaded. The load events above likely won't be raised
|
||||
setTimeout(callback, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
jdenticon-js/src/common/global.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
|
||||
// backward compatibility.
|
||||
|
||||
export const GLOBAL =
|
||||
typeof window !== "undefined" ? window :
|
||||
typeof self !== "undefined" ? self :
|
||||
typeof global !== "undefined" ? global :
|
||||
{};
|
||||
10
jdenticon-js/src/common/global.umd.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
/* global umdGlobal */
|
||||
|
||||
// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
|
||||
// backward compatibility.
|
||||
export const GLOBAL = umdGlobal;
|
||||
23
jdenticon-js/src/common/hashUtils.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { sha1 } from "./sha1";
|
||||
|
||||
/**
|
||||
* Inputs a value that might be a valid hash string for Jdenticon and returns it
|
||||
* if it is determined valid, otherwise a falsy value is returned.
|
||||
*/
|
||||
export function isValidHash(hashCandidate) {
|
||||
return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash for the specified value. Currently SHA1 is used. This function
|
||||
* always returns a valid hash.
|
||||
*/
|
||||
export function computeHash(value) {
|
||||
return sha1(value == null ? "" : "" + value);
|
||||
}
|
||||
47
jdenticon-js/src/common/observer.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { ATTRIBUTES, ICON_SELECTOR, getIdenticonType } from "./dom";
|
||||
|
||||
export function observer(updateCallback) {
|
||||
if (typeof MutationObserver != "undefined") {
|
||||
const mutationObserver = new MutationObserver(function onmutation(mutations) {
|
||||
for (let mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
|
||||
const mutation = mutations[mutationIndex];
|
||||
const addedNodes = mutation.addedNodes;
|
||||
|
||||
for (let addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
|
||||
const addedNode = addedNodes[addedNodeIndex];
|
||||
|
||||
// Skip other types of nodes than element nodes, since they might not support
|
||||
// the querySelectorAll method => runtime error.
|
||||
if (addedNode.nodeType == Node.ELEMENT_NODE) {
|
||||
if (getIdenticonType(addedNode)) {
|
||||
updateCallback(addedNode);
|
||||
}
|
||||
else {
|
||||
const icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
|
||||
for (let iconIndex = 0; iconIndex < icons.length; iconIndex++) {
|
||||
updateCallback(icons[iconIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
|
||||
updateCallback(mutation.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mutationObserver.observe(document.body, {
|
||||
"childList": true,
|
||||
"attributes": true,
|
||||
"attributeFilter": [ATTRIBUTES.VALUE, ATTRIBUTES.HASH, "width", "height"],
|
||||
"subtree": true,
|
||||
});
|
||||
}
|
||||
}
|
||||
14
jdenticon-js/src/common/parseHex.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses a substring of the hash as a number.
|
||||
* @param {number} startPosition
|
||||
* @param {number=} octets
|
||||
*/
|
||||
export function parseHex(hash, startPosition, octets) {
|
||||
return parseInt(hash.substr(startPosition, octets), 16);
|
||||
}
|
||||
132
jdenticon-js/src/common/sha1.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
/**
|
||||
* Computes a SHA1 hash for any value and returns it as a hexadecimal string.
|
||||
*
|
||||
* This function is optimized for minimal code size and rather short messages.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
export function sha1(message) {
|
||||
const HASH_SIZE_HALF_BYTES = 40;
|
||||
const BLOCK_SIZE_WORDS = 16;
|
||||
|
||||
// Variables
|
||||
// `var` is used to be able to minimize the number of `var` keywords.
|
||||
var i = 0,
|
||||
f = 0,
|
||||
|
||||
// Use `encodeURI` to UTF8 encode the message without any additional libraries
|
||||
// We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
|
||||
// since `unescape` is deprecated.
|
||||
urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
|
||||
|
||||
// This can be changed to a preallocated Uint32Array array for greater performance and larger code size
|
||||
data = [],
|
||||
dataSize,
|
||||
|
||||
hashBuffer = [],
|
||||
|
||||
a = 0x67452301,
|
||||
b = 0xefcdab89,
|
||||
c = ~a,
|
||||
d = ~b,
|
||||
e = 0xc3d2e1f0,
|
||||
hash = [a, b, c, d, e],
|
||||
|
||||
blockStartIndex = 0,
|
||||
hexHash = "";
|
||||
|
||||
/**
|
||||
* Rotates the value a specified number of bits to the left.
|
||||
* @param {number} value Value to rotate
|
||||
* @param {number} shift Bit count to shift.
|
||||
*/
|
||||
function rotl(value, shift) {
|
||||
return (value << shift) | (value >>> (32 - shift));
|
||||
}
|
||||
|
||||
// Message data
|
||||
for ( ; i < urlEncodedMessage.length; f++) {
|
||||
data[f >> 2] = data[f >> 2] |
|
||||
(
|
||||
(
|
||||
urlEncodedMessage[i] == "%"
|
||||
// Percent encoded byte
|
||||
? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
|
||||
// Unencoded byte
|
||||
: urlEncodedMessage.charCodeAt(i++)
|
||||
)
|
||||
|
||||
// Read bytes in reverse order (big endian words)
|
||||
<< ((3 - (f & 3)) * 8)
|
||||
);
|
||||
}
|
||||
|
||||
// f is now the length of the utf8 encoded message
|
||||
// 7 = 8 bytes (64 bit) for message size, -1 to round down
|
||||
// >> 6 = integer division with block size
|
||||
dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
|
||||
|
||||
// Message size in bits.
|
||||
// SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
|
||||
// significant 32 bits are set. -8 is for the '1' bit padding byte.
|
||||
data[dataSize - 1] = f * 8 - 8;
|
||||
|
||||
// Compute hash
|
||||
for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
|
||||
for (i = 0; i < 80; i++) {
|
||||
f = rotl(a, 5) + e + (
|
||||
// Ch
|
||||
i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
|
||||
|
||||
// Parity
|
||||
i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
|
||||
|
||||
// Maj
|
||||
i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
|
||||
|
||||
// Parity
|
||||
(b ^ c ^ d) + 0xca62c1d6
|
||||
) + (
|
||||
hashBuffer[i] = i < BLOCK_SIZE_WORDS
|
||||
// Bitwise OR is used to coerse `undefined` to 0
|
||||
? (data[blockStartIndex + i] | 0)
|
||||
: rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
|
||||
);
|
||||
|
||||
e = d;
|
||||
d = c;
|
||||
c = rotl(b, 30);
|
||||
b = a;
|
||||
a = f;
|
||||
}
|
||||
|
||||
hash[0] = a = ((hash[0] + a) | 0);
|
||||
hash[1] = b = ((hash[1] + b) | 0);
|
||||
hash[2] = c = ((hash[2] + c) | 0);
|
||||
hash[3] = d = ((hash[3] + d) | 0);
|
||||
hash[4] = e = ((hash[4] + e) | 0);
|
||||
}
|
||||
|
||||
// Format hex hash
|
||||
for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
|
||||
hexHash += (
|
||||
(
|
||||
// Get word (2^3 half-bytes per word)
|
||||
hash[i >> 3] >>>
|
||||
|
||||
// Append half-bytes in reverse order
|
||||
((7 - (i & 7)) * 4)
|
||||
)
|
||||
// Clamp to half-byte
|
||||
& 0xf
|
||||
).toString(16);
|
||||
}
|
||||
|
||||
return hexHash;
|
||||
}
|
||||
72
jdenticon-js/src/node-cjs.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
// This file is compiled to dist/jdenticon-node.js
|
||||
|
||||
if (typeof process === "undefined" &&
|
||||
typeof window !== "undefined" &&
|
||||
typeof document !== "undefined"
|
||||
) {
|
||||
console.warn(
|
||||
"Jdenticon: 'dist/jdenticon-node.js' is only intended for Node.js environments and will increase your " +
|
||||
"bundle size when included in browser bundles. If you want to run Jdenticon in the browser, please add a " +
|
||||
"reference to 'dist/jdenticon.js' or 'dist/jdenticon.min.js' instead.");
|
||||
}
|
||||
|
||||
import { defineConfigPropertyWithWarn } from "./common/configuration";
|
||||
import { configure } from "./apis/configure";
|
||||
import { drawIcon } from "./apis/drawIcon";
|
||||
import { toPng } from "./apis/toPng";
|
||||
import { toSvg } from "./apis/toSvg";
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
function jdenticon() {
|
||||
throw new Error("jdenticon() is not supported on Node.js.");
|
||||
}
|
||||
|
||||
defineConfigPropertyWithWarn(jdenticon);
|
||||
|
||||
jdenticon.configure = configure;
|
||||
jdenticon.drawIcon = drawIcon;
|
||||
jdenticon.toPng = toPng;
|
||||
jdenticon.toSvg = toSvg;
|
||||
|
||||
/**
|
||||
* Specifies the version of the Jdenticon package in use.
|
||||
* @type {string}
|
||||
*/
|
||||
jdenticon.version = "#version#";
|
||||
|
||||
/**
|
||||
* Specifies which bundle of Jdenticon that is used.
|
||||
* @type {string}
|
||||
*/
|
||||
jdenticon.bundle = "node-cjs";
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
jdenticon.update = function update() {
|
||||
throw new Error("jdenticon.update() is not supported on Node.js.");
|
||||
};
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
jdenticon.updateCanvas = function updateCanvas() {
|
||||
throw new Error("jdenticon.updateCanvas() is not supported on Node.js.");
|
||||
};
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
jdenticon.updateSvg = function updateSvg() {
|
||||
throw new Error("jdenticon.updateSvg() is not supported on Node.js.");
|
||||
};
|
||||
|
||||
module.exports = jdenticon;
|
||||
55
jdenticon-js/src/node-esm.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
// This file is compiled to dist/jdenticon-node.mjs
|
||||
|
||||
if (typeof process === "undefined" &&
|
||||
typeof window !== "undefined" &&
|
||||
typeof document !== "undefined"
|
||||
) {
|
||||
console.warn(
|
||||
"Jdenticon: 'dist/jdenticon-node.mjs' is only intended for Node.js environments and will increase your " +
|
||||
"bundle size when included in browser bundles. If you want to run Jdenticon in the browser, please add a " +
|
||||
"reference to 'dist/jdenticon.js' or 'dist/jdenticon.min.js' instead.");
|
||||
}
|
||||
|
||||
export { configure } from "./apis/configure";
|
||||
export { drawIcon } from "./apis/drawIcon";
|
||||
export { toPng } from "./apis/toPng";
|
||||
export { toSvg } from "./apis/toSvg";
|
||||
|
||||
/**
|
||||
* Specifies the version of the Jdenticon package in use.
|
||||
* @type {string}
|
||||
*/
|
||||
export const version = "#version#";
|
||||
|
||||
/**
|
||||
* Specifies which bundle of Jdenticon that is used.
|
||||
* @type {string}
|
||||
*/
|
||||
export const bundle = "node-esm";
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
export function update() {
|
||||
throw new Error("jdenticon.update() is not supported on Node.js.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
export function updateCanvas() {
|
||||
throw new Error("jdenticon.updateCanvas() is not supported on Node.js.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws {Error}
|
||||
*/
|
||||
export function updateSvg() {
|
||||
throw new Error("jdenticon.updateSvg() is not supported on Node.js.");
|
||||
}
|
||||
3
jdenticon-js/src/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
108
jdenticon-js/src/renderer/canvas/canvasRenderer.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { toCss3Color } from "../color";
|
||||
|
||||
/**
|
||||
* @typedef {import("../renderer").Renderer} Renderer
|
||||
* @typedef {import('../point').Point} Point
|
||||
*/
|
||||
|
||||
/**
|
||||
* Renderer redirecting drawing commands to a canvas context.
|
||||
* @implements {Renderer}
|
||||
*/
|
||||
export class CanvasRenderer {
|
||||
/**
|
||||
* @param {number=} iconSize
|
||||
*/
|
||||
constructor(ctx, iconSize) {
|
||||
const canvas = ctx.canvas;
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
ctx.save();
|
||||
|
||||
if (!iconSize) {
|
||||
iconSize = Math.min(width, height);
|
||||
|
||||
ctx.translate(
|
||||
((width - iconSize) / 2) | 0,
|
||||
((height - iconSize) / 2) | 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this._ctx = ctx;
|
||||
this.iconSize = iconSize;
|
||||
|
||||
ctx.clearRect(0, 0, iconSize, iconSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the background with the specified color.
|
||||
* @param {string} fillColor Fill color on the format #rrggbb[aa].
|
||||
*/
|
||||
setBackground(fillColor) {
|
||||
const ctx = this._ctx;
|
||||
const iconSize = this.iconSize;
|
||||
|
||||
ctx.fillStyle = toCss3Color(fillColor);
|
||||
ctx.fillRect(0, 0, iconSize, iconSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
|
||||
* @param {string} fillColor Fill color on format #rrggbb[aa].
|
||||
*/
|
||||
beginShape(fillColor) {
|
||||
const ctx = this._ctx;
|
||||
ctx.fillStyle = toCss3Color(fillColor);
|
||||
ctx.beginPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
|
||||
*/
|
||||
endShape() {
|
||||
this._ctx.fill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a polygon to the rendering queue.
|
||||
* @param points An array of Point objects.
|
||||
*/
|
||||
addPolygon(points) {
|
||||
const ctx = this._ctx;
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
ctx.lineTo(points[i].x, points[i].y);
|
||||
}
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a circle to the rendering queue.
|
||||
* @param {Point} point The upper left corner of the circle bounding box.
|
||||
* @param {number} diameter The diameter of the circle.
|
||||
* @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
|
||||
*/
|
||||
addCircle(point, diameter, counterClockwise) {
|
||||
const ctx = this._ctx,
|
||||
radius = diameter / 2;
|
||||
ctx.moveTo(point.x + radius, point.y + radius);
|
||||
ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the icon has been completely drawn.
|
||||
*/
|
||||
finish() {
|
||||
this._ctx.restore();
|
||||
}
|
||||
}
|
||||
123
jdenticon-js/src/renderer/color.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { parseHex } from "../common/parseHex";
|
||||
|
||||
function decToHex(v) {
|
||||
v |= 0; // Ensure integer value
|
||||
return v < 0 ? "00" :
|
||||
v < 16 ? "0" + v.toString(16) :
|
||||
v < 256 ? v.toString(16) :
|
||||
"ff";
|
||||
}
|
||||
|
||||
function hueToRgb(m1, m2, h) {
|
||||
h = h < 0 ? h + 6 : h > 6 ? h - 6 : h;
|
||||
return decToHex(255 * (
|
||||
h < 1 ? m1 + (m2 - m1) * h :
|
||||
h < 3 ? m2 :
|
||||
h < 4 ? m1 + (m2 - m1) * (4 - h) :
|
||||
m1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} r Red channel [0, 255]
|
||||
* @param {number} g Green channel [0, 255]
|
||||
* @param {number} b Blue channel [0, 255]
|
||||
*/
|
||||
export function rgb(r, g, b) {
|
||||
return "#" + decToHex(r) + decToHex(g) + decToHex(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parseColor(color) {
|
||||
if (/^#[0-9a-f]{3,8}$/i.test(color)) {
|
||||
let result;
|
||||
const colorLength = color.length;
|
||||
|
||||
if (colorLength < 6) {
|
||||
const r = color[1],
|
||||
g = color[2],
|
||||
b = color[3],
|
||||
a = color[4] || "";
|
||||
result = "#" + r + r + g + g + b + b + a + a;
|
||||
}
|
||||
if (colorLength == 7 || colorLength > 8) {
|
||||
result = color;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hexadecimal color to a CSS3 compatible color.
|
||||
* @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
|
||||
* @returns {string}
|
||||
*/
|
||||
export function toCss3Color(hexColor) {
|
||||
const a = parseHex(hexColor, 7, 2);
|
||||
let result;
|
||||
|
||||
if (isNaN(a)) {
|
||||
result = hexColor;
|
||||
} else {
|
||||
const r = parseHex(hexColor, 1, 2),
|
||||
g = parseHex(hexColor, 3, 2),
|
||||
b = parseHex(hexColor, 5, 2);
|
||||
result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color to a hexadecimal RGB color.
|
||||
* @param {number} hue Hue in range [0, 1]
|
||||
* @param {number} saturation Saturation in range [0, 1]
|
||||
* @param {number} lightness Lightness in range [0, 1]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function hsl(hue, saturation, lightness) {
|
||||
// Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
|
||||
let result;
|
||||
|
||||
if (saturation == 0) {
|
||||
const partialHex = decToHex(lightness * 255);
|
||||
result = partialHex + partialHex + partialHex;
|
||||
}
|
||||
else {
|
||||
const m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
|
||||
m1 = lightness * 2 - m2;
|
||||
result =
|
||||
hueToRgb(m1, m2, hue * 6 + 2) +
|
||||
hueToRgb(m1, m2, hue * 6) +
|
||||
hueToRgb(m1, m2, hue * 6 - 2);
|
||||
}
|
||||
|
||||
return "#" + result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
|
||||
* @param {number} hue Hue in range [0, 1]
|
||||
* @param {number} saturation Saturation in range [0, 1]
|
||||
* @param {number} lightness Lightness in range [0, 1]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function correctedHsl(hue, saturation, lightness) {
|
||||
// The corrector specifies the perceived middle lightness for each hue
|
||||
const correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
|
||||
corrector = correctors[(hue * 6 + 0.5) | 0];
|
||||
|
||||
// Adjust the input lightness relative to the corrector
|
||||
lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
|
||||
|
||||
return hsl(hue, saturation, lightness);
|
||||
}
|
||||
28
jdenticon-js/src/renderer/colorTheme.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { correctedHsl } from "./color";
|
||||
|
||||
/**
|
||||
* Gets a set of identicon color candidates for a specified hue and config.
|
||||
* @param {number} hue
|
||||
* @param {import("../common/configuration").ParsedConfiguration} config
|
||||
*/
|
||||
export function colorTheme(hue, config) {
|
||||
hue = config.hue(hue);
|
||||
return [
|
||||
// Dark gray
|
||||
correctedHsl(hue, config.grayscaleSaturation, config.grayscaleLightness(0)),
|
||||
// Mid color
|
||||
correctedHsl(hue, config.colorSaturation, config.colorLightness(0.5)),
|
||||
// Light gray
|
||||
correctedHsl(hue, config.grayscaleSaturation, config.grayscaleLightness(1)),
|
||||
// Light color
|
||||
correctedHsl(hue, config.colorSaturation, config.colorLightness(1)),
|
||||
// Dark color
|
||||
correctedHsl(hue, config.colorSaturation, config.colorLightness(0))
|
||||
];
|
||||
}
|
||||
116
jdenticon-js/src/renderer/graphics.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { NO_TRANSFORM } from "./transform";
|
||||
|
||||
/**
|
||||
* @typedef {import("./renderer").Renderer} Renderer
|
||||
* @typedef {import("./transform").Transform} Transform
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides helper functions for rendering common basic shapes.
|
||||
*/
|
||||
export class Graphics {
|
||||
/**
|
||||
* @param {Renderer} renderer
|
||||
*/
|
||||
constructor(renderer) {
|
||||
/**
|
||||
* @type {Renderer}
|
||||
* @private
|
||||
*/
|
||||
this._renderer = renderer;
|
||||
|
||||
/**
|
||||
* @type {Transform}
|
||||
*/
|
||||
this.currentTransform = NO_TRANSFORM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a polygon to the underlying renderer.
|
||||
* @param {Array<number>} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ]
|
||||
* @param {boolean=} invert Specifies if the polygon will be inverted.
|
||||
*/
|
||||
addPolygon(points, invert) {
|
||||
const di = invert ? -2 : 2,
|
||||
transformedPoints = [];
|
||||
|
||||
for (let i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
|
||||
transformedPoints.push(this.currentTransform.transformIconPoint(points[i], points[i + 1]));
|
||||
}
|
||||
|
||||
this._renderer.addPolygon(transformedPoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a polygon to the underlying renderer.
|
||||
* Source: http://stackoverflow.com/a/2173084
|
||||
* @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse.
|
||||
* @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse.
|
||||
* @param {number} size The size of the ellipse.
|
||||
* @param {boolean=} invert Specifies if the ellipse will be inverted.
|
||||
*/
|
||||
addCircle(x, y, size, invert) {
|
||||
const p = this.currentTransform.transformIconPoint(x, y, size, size);
|
||||
this._renderer.addCircle(p, size, invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rectangle to the underlying renderer.
|
||||
* @param {number} x The x-coordinate of the upper left corner of the rectangle.
|
||||
* @param {number} y The y-coordinate of the upper left corner of the rectangle.
|
||||
* @param {number} w The width of the rectangle.
|
||||
* @param {number} h The height of the rectangle.
|
||||
* @param {boolean=} invert Specifies if the rectangle will be inverted.
|
||||
*/
|
||||
addRectangle(x, y, w, h, invert) {
|
||||
this.addPolygon([
|
||||
x, y,
|
||||
x + w, y,
|
||||
x + w, y + h,
|
||||
x, y + h
|
||||
], invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a right triangle to the underlying renderer.
|
||||
* @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle.
|
||||
* @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle.
|
||||
* @param {number} w The width of the triangle.
|
||||
* @param {number} h The height of the triangle.
|
||||
* @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle.
|
||||
* @param {boolean=} invert Specifies if the triangle will be inverted.
|
||||
*/
|
||||
addTriangle(x, y, w, h, r, invert) {
|
||||
const points = [
|
||||
x + w, y,
|
||||
x + w, y + h,
|
||||
x, y + h,
|
||||
x, y
|
||||
];
|
||||
points.splice(((r || 0) % 4) * 2, 2);
|
||||
this.addPolygon(points, invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a rhombus to the underlying renderer.
|
||||
* @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus.
|
||||
* @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus.
|
||||
* @param {number} w The width of the rhombus.
|
||||
* @param {number} h The height of the rhombus.
|
||||
* @param {boolean=} invert Specifies if the rhombus will be inverted.
|
||||
*/
|
||||
addRhombus(x, y, w, h, invert) {
|
||||
this.addPolygon([
|
||||
x + w / 2, y,
|
||||
x + w, y + h / 2,
|
||||
x + w / 2, y + h,
|
||||
x, y + h / 2
|
||||
], invert);
|
||||
}
|
||||
}
|
||||
95
jdenticon-js/src/renderer/iconGenerator.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { Transform } from "./transform";
|
||||
import { Graphics } from "./graphics";
|
||||
import { centerShape, outerShape } from "./shapes";
|
||||
import { colorTheme } from "./colorTheme";
|
||||
import { parseHex } from "../common/parseHex";
|
||||
import { getConfiguration } from "../common/configuration";
|
||||
|
||||
/**
|
||||
* Draws an identicon to a specified renderer.
|
||||
* @param {import('./renderer').Renderer} renderer
|
||||
* @param {string} hash
|
||||
* @param {Object|number=} config
|
||||
*/
|
||||
export function iconGenerator(renderer, hash, config) {
|
||||
const parsedConfig = getConfiguration(config, 0.08);
|
||||
|
||||
// Set background color
|
||||
if (parsedConfig.backColor) {
|
||||
renderer.setBackground(parsedConfig.backColor);
|
||||
}
|
||||
|
||||
// Calculate padding and round to nearest integer
|
||||
let size = renderer.iconSize;
|
||||
const padding = (0.5 + size * parsedConfig.iconPadding) | 0;
|
||||
size -= padding * 2;
|
||||
|
||||
const graphics = new Graphics(renderer);
|
||||
|
||||
// Calculate cell size and ensure it is an integer
|
||||
const cell = 0 | (size / 4);
|
||||
|
||||
// Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon
|
||||
const x = 0 | (padding + size / 2 - cell * 2);
|
||||
const y = 0 | (padding + size / 2 - cell * 2);
|
||||
|
||||
function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
|
||||
const shapeIndex = parseHex(hash, index, 1);
|
||||
let r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
|
||||
|
||||
renderer.beginShape(availableColors[selectedColorIndexes[colorIndex]]);
|
||||
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
graphics.currentTransform = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
|
||||
shapes(shapeIndex, graphics, cell, i);
|
||||
}
|
||||
|
||||
renderer.endShape();
|
||||
}
|
||||
|
||||
// AVAILABLE COLORS
|
||||
const hue = parseHex(hash, -7) / 0xfffffff,
|
||||
|
||||
// Available colors for this icon
|
||||
availableColors = colorTheme(hue, parsedConfig),
|
||||
|
||||
// The index of the selected colors
|
||||
selectedColorIndexes = [];
|
||||
|
||||
let index;
|
||||
|
||||
function isDuplicate(values) {
|
||||
if (values.indexOf(index) >= 0) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
if (selectedColorIndexes.indexOf(values[i]) >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
index = parseHex(hash, 8 + i, 1) % availableColors.length;
|
||||
if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo
|
||||
isDuplicate([2, 3])) { // Disallow light gray and light color combo
|
||||
index = 1;
|
||||
}
|
||||
selectedColorIndexes.push(index);
|
||||
}
|
||||
|
||||
// ACTUAL RENDERING
|
||||
// Sides
|
||||
renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
|
||||
// Corners
|
||||
renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
|
||||
// Center
|
||||
renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
|
||||
|
||||
renderer.finish();
|
||||
}
|
||||
19
jdenticon-js/src/renderer/point.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a point.
|
||||
*/
|
||||
export class Point {
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*/
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
18
jdenticon-js/src/renderer/renderer.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @typedef {import('./point').Point} Point
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Renderer
|
||||
* @property {number} iconSize
|
||||
* @property {function(string):void} setBackground Fills the background with the specified color.
|
||||
* @property {function(string):void} beginShape Marks the beginning of a new shape of the specified color. Should be
|
||||
* ended with a call to endShape.
|
||||
* @property {function():void} endShape Marks the end of the currently drawn shape. This causes the queued paths to
|
||||
* be rendered on the canvas.
|
||||
* @property {function(Point[]):void} addPolygon Adds a polygon to the rendering queue.
|
||||
* @property {function(Point,number,boolean):void} addCircle Adds a circle to the rendering queue.
|
||||
* @property {function():void} finish Called when the icon has been completely drawn.
|
||||
*/
|
||||
|
||||
export default class {}
|
||||
154
jdenticon-js/src/renderer/shapes.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @param {Graphics} g
|
||||
* @param {number} cell
|
||||
* @param {number} positionIndex
|
||||
* @typedef {import('./graphics').Graphics} Graphics
|
||||
*/
|
||||
export function centerShape(index, g, cell, positionIndex) {
|
||||
index = index % 14;
|
||||
|
||||
let k, m, w, h, inner, outer;
|
||||
|
||||
!index ? (
|
||||
k = cell * 0.42,
|
||||
g.addPolygon([
|
||||
0, 0,
|
||||
cell, 0,
|
||||
cell, cell - k * 2,
|
||||
cell - k, cell,
|
||||
0, cell
|
||||
])) :
|
||||
|
||||
index == 1 ? (
|
||||
w = 0 | (cell * 0.5),
|
||||
h = 0 | (cell * 0.8),
|
||||
|
||||
g.addTriangle(cell - w, 0, w, h, 2)) :
|
||||
|
||||
index == 2 ? (
|
||||
w = 0 | (cell / 3),
|
||||
g.addRectangle(w, w, cell - w, cell - w)) :
|
||||
|
||||
index == 3 ? (
|
||||
inner = cell * 0.1,
|
||||
// Use fixed outer border widths in small icons to ensure the border is drawn
|
||||
outer =
|
||||
cell < 6 ? 1 :
|
||||
cell < 8 ? 2 :
|
||||
(0 | (cell * 0.25)),
|
||||
|
||||
inner =
|
||||
inner > 1 ? (0 | inner) : // large icon => truncate decimals
|
||||
inner > 0.5 ? 1 : // medium size icon => fixed width
|
||||
inner, // small icon => anti-aliased border
|
||||
|
||||
g.addRectangle(outer, outer, cell - inner - outer, cell - inner - outer)) :
|
||||
|
||||
index == 4 ? (
|
||||
m = 0 | (cell * 0.15),
|
||||
w = 0 | (cell * 0.5),
|
||||
g.addCircle(cell - w - m, cell - w - m, w)) :
|
||||
|
||||
index == 5 ? (
|
||||
inner = cell * 0.1,
|
||||
outer = inner * 4,
|
||||
|
||||
// Align edge to nearest pixel in large icons
|
||||
outer > 3 && (outer = 0 | outer),
|
||||
|
||||
g.addRectangle(0, 0, cell, cell),
|
||||
g.addPolygon([
|
||||
outer, outer,
|
||||
cell - inner, outer,
|
||||
outer + (cell - outer - inner) / 2, cell - inner
|
||||
], true)) :
|
||||
|
||||
index == 6 ?
|
||||
g.addPolygon([
|
||||
0, 0,
|
||||
cell, 0,
|
||||
cell, cell * 0.7,
|
||||
cell * 0.4, cell * 0.4,
|
||||
cell * 0.7, cell,
|
||||
0, cell
|
||||
]) :
|
||||
|
||||
index == 7 ?
|
||||
g.addTriangle(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
|
||||
|
||||
index == 8 ? (
|
||||
g.addRectangle(0, 0, cell, cell / 2),
|
||||
g.addRectangle(0, cell / 2, cell / 2, cell / 2),
|
||||
g.addTriangle(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
|
||||
|
||||
index == 9 ? (
|
||||
inner = cell * 0.14,
|
||||
// Use fixed outer border widths in small icons to ensure the border is drawn
|
||||
outer =
|
||||
cell < 4 ? 1 :
|
||||
cell < 6 ? 2 :
|
||||
(0 | (cell * 0.35)),
|
||||
|
||||
inner =
|
||||
cell < 8 ? inner : // small icon => anti-aliased border
|
||||
(0 | inner), // large icon => truncate decimals
|
||||
|
||||
g.addRectangle(0, 0, cell, cell),
|
||||
g.addRectangle(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
|
||||
|
||||
index == 10 ? (
|
||||
inner = cell * 0.12,
|
||||
outer = inner * 3,
|
||||
|
||||
g.addRectangle(0, 0, cell, cell),
|
||||
g.addCircle(outer, outer, cell - inner - outer, true)) :
|
||||
|
||||
index == 11 ?
|
||||
g.addTriangle(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
|
||||
|
||||
index == 12 ? (
|
||||
m = cell * 0.25,
|
||||
g.addRectangle(0, 0, cell, cell),
|
||||
g.addRhombus(m, m, cell - m, cell - m, true)) :
|
||||
|
||||
// 13
|
||||
(
|
||||
!positionIndex && (
|
||||
m = cell * 0.4, w = cell * 1.2,
|
||||
g.addCircle(m, m, w)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @param {Graphics} g
|
||||
* @param {number} cell
|
||||
*/
|
||||
export function outerShape(index, g, cell) {
|
||||
index = index % 4;
|
||||
|
||||
let m;
|
||||
|
||||
!index ?
|
||||
g.addTriangle(0, 0, cell, cell, 0) :
|
||||
|
||||
index == 1 ?
|
||||
g.addTriangle(0, cell / 2, cell, cell / 2, 0) :
|
||||
|
||||
index == 2 ?
|
||||
g.addRhombus(0, 0, cell, cell) :
|
||||
|
||||
// 3
|
||||
(
|
||||
m = cell / 6,
|
||||
g.addCircle(m, m, cell - 2 * m)
|
||||
);
|
||||
}
|
||||
11
jdenticon-js/src/renderer/svg/constants.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
export const SVG_CONSTANTS = {
|
||||
XMLNS: "http://www.w3.org/2000/svg",
|
||||
WIDTH: "width",
|
||||
HEIGHT: "height",
|
||||
}
|
||||
88
jdenticon-js/src/renderer/svg/svgElement.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { SVG_CONSTANTS } from "./constants";
|
||||
|
||||
/**
|
||||
* Creates a new element and adds it to the specified parent.
|
||||
* @param {Element} parentNode
|
||||
* @param {string} name
|
||||
* @param {...(string|number)} keyValuePairs
|
||||
*/
|
||||
function SvgElement_append(parentNode, name, ...keyValuePairs) {
|
||||
const el = document.createElementNS(SVG_CONSTANTS.XMLNS, name);
|
||||
|
||||
for (let i = 0; i + 1 < keyValuePairs.length; i += 2) {
|
||||
el.setAttribute(
|
||||
/** @type {string} */(keyValuePairs[i]),
|
||||
/** @type {string} */(keyValuePairs[i + 1]),
|
||||
);
|
||||
}
|
||||
|
||||
parentNode.appendChild(el);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renderer producing SVG output.
|
||||
*/
|
||||
export class SvgElement {
|
||||
/**
|
||||
* @param {Element} element - Target element
|
||||
*/
|
||||
constructor(element) {
|
||||
// Don't use the clientWidth and clientHeight properties on SVG elements
|
||||
// since Firefox won't serve a proper value of these properties on SVG
|
||||
// elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
|
||||
// Instead use 100px as a hardcoded size (the svg viewBox will rescale
|
||||
// the icon to the correct dimensions)
|
||||
const iconSize = this.iconSize = Math.min(
|
||||
(Number(element.getAttribute(SVG_CONSTANTS.WIDTH)) || 100),
|
||||
(Number(element.getAttribute(SVG_CONSTANTS.HEIGHT)) || 100)
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this._el = element;
|
||||
|
||||
// Clear current SVG child elements
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
|
||||
// Set viewBox attribute to ensure the svg scales nicely.
|
||||
element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
|
||||
element.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the background with the specified color.
|
||||
* @param {string} fillColor Fill color on the format #rrggbb.
|
||||
* @param {number} opacity Opacity in the range [0.0, 1.0].
|
||||
*/
|
||||
setBackground(fillColor, opacity) {
|
||||
if (opacity) {
|
||||
SvgElement_append(this._el, "rect",
|
||||
SVG_CONSTANTS.WIDTH, "100%",
|
||||
SVG_CONSTANTS.HEIGHT, "100%",
|
||||
"fill", fillColor,
|
||||
"opacity", opacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a path to the SVG element.
|
||||
* @param {string} color Fill color on format #xxxxxx.
|
||||
* @param {string} dataString The SVG path data string.
|
||||
*/
|
||||
appendPath(color, dataString) {
|
||||
SvgElement_append(this._el, "path",
|
||||
"fill", color,
|
||||
"d", dataString);
|
||||
}
|
||||
}
|
||||
58
jdenticon-js/src/renderer/svg/svgPath.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prepares a measure to be used as a measure in an SVG path, by
|
||||
* rounding the measure to a single decimal. This reduces the file
|
||||
* size of the generated SVG with more than 50% in some cases.
|
||||
*/
|
||||
function svgValue(value) {
|
||||
return ((value * 10 + 0.5) | 0) / 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an SVG path element.
|
||||
*/
|
||||
export class SvgPath {
|
||||
constructor() {
|
||||
/**
|
||||
* This property holds the data string (path.d) of the SVG path.
|
||||
* @type {string}
|
||||
*/
|
||||
this.dataString = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a polygon with the current fill color to the SVG path.
|
||||
* @param points An array of Point objects.
|
||||
*/
|
||||
addPolygon(points) {
|
||||
let dataString = "";
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
|
||||
}
|
||||
this.dataString += dataString + "Z";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a circle with the current fill color to the SVG path.
|
||||
* @param {import('../point').Point} point The upper left corner of the circle bounding box.
|
||||
* @param {number} diameter The diameter of the circle.
|
||||
* @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
|
||||
*/
|
||||
addCircle(point, diameter, counterClockwise) {
|
||||
const sweepFlag = counterClockwise ? 0 : 1,
|
||||
svgRadius = svgValue(diameter / 2),
|
||||
svgDiameter = svgValue(diameter),
|
||||
svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
|
||||
|
||||
this.dataString +=
|
||||
"M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
|
||||
svgArc + svgDiameter + ",0" +
|
||||
svgArc + (-svgDiameter) + ",0";
|
||||
}
|
||||
}
|
||||
|
||||
104
jdenticon-js/src/renderer/svg/svgRenderer.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { SvgPath } from "./svgPath";
|
||||
import { parseHex } from "../../common/parseHex";
|
||||
|
||||
/**
|
||||
* @typedef {import("../point").Point} Point
|
||||
* @typedef {import("../renderer").Renderer} Renderer
|
||||
* @typedef {import("./svgElement").SvgElement} SvgElement
|
||||
* @typedef {import("./svgWriter").SvgWriter} SvgWriter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Renderer producing SVG output.
|
||||
* @implements {Renderer}
|
||||
*/
|
||||
export class SvgRenderer {
|
||||
/**
|
||||
* @param {SvgElement|SvgWriter} target
|
||||
*/
|
||||
constructor(target) {
|
||||
/**
|
||||
* @type {SvgPath}
|
||||
* @private
|
||||
*/
|
||||
this._path;
|
||||
|
||||
/**
|
||||
* @type {Object.<string,SvgPath>}
|
||||
* @private
|
||||
*/
|
||||
this._pathsByColor = { };
|
||||
|
||||
/**
|
||||
* @type {SvgElement|SvgWriter}
|
||||
* @private
|
||||
*/
|
||||
this._target = target;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.iconSize = target.iconSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the background with the specified color.
|
||||
* @param {string} fillColor Fill color on the format #rrggbb[aa].
|
||||
*/
|
||||
setBackground(fillColor) {
|
||||
const match = /^(#......)(..)?/.exec(fillColor),
|
||||
opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
|
||||
this._target.setBackground(match[1], opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
|
||||
* @param {string} color Fill color on format #xxxxxx.
|
||||
*/
|
||||
beginShape(color) {
|
||||
this._path = this._pathsByColor[color] || (this._pathsByColor[color] = new SvgPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the end of the currently drawn shape.
|
||||
*/
|
||||
endShape() { }
|
||||
|
||||
/**
|
||||
* Adds a polygon with the current fill color to the SVG.
|
||||
* @param points An array of Point objects.
|
||||
*/
|
||||
addPolygon(points) {
|
||||
this._path.addPolygon(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a circle with the current fill color to the SVG.
|
||||
* @param {Point} point The upper left corner of the circle bounding box.
|
||||
* @param {number} diameter The diameter of the circle.
|
||||
* @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
|
||||
*/
|
||||
addCircle(point, diameter, counterClockwise) {
|
||||
this._path.addCircle(point, diameter, counterClockwise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the icon has been completely drawn.
|
||||
*/
|
||||
finish() {
|
||||
const pathsByColor = this._pathsByColor;
|
||||
for (let color in pathsByColor) {
|
||||
// hasOwnProperty cannot be shadowed in pathsByColor
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (pathsByColor.hasOwnProperty(color)) {
|
||||
this._target.appendPath(color, pathsByColor[color].dataString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
jdenticon-js/src/renderer/svg/svgWriter.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { SVG_CONSTANTS } from "./constants";
|
||||
|
||||
/**
|
||||
* Renderer producing SVG output.
|
||||
*/
|
||||
export class SvgWriter {
|
||||
/**
|
||||
* @param {number} iconSize - Icon width and height in pixels.
|
||||
*/
|
||||
constructor(iconSize) {
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.iconSize = iconSize;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this._s =
|
||||
'<svg xmlns="' + SVG_CONSTANTS.XMLNS + '" width="' +
|
||||
iconSize + '" height="' + iconSize + '" viewBox="0 0 ' +
|
||||
iconSize + ' ' + iconSize + '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the background with the specified color.
|
||||
* @param {string} fillColor Fill color on the format #rrggbb.
|
||||
* @param {number} opacity Opacity in the range [0.0, 1.0].
|
||||
*/
|
||||
setBackground(fillColor, opacity) {
|
||||
if (opacity) {
|
||||
this._s += '<rect width="100%" height="100%" fill="' +
|
||||
fillColor + '" opacity="' + opacity.toFixed(2) + '"/>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a path to the SVG string.
|
||||
* @param {string} color Fill color on format #rrggbb.
|
||||
* @param {string} dataString The SVG path data string.
|
||||
*/
|
||||
appendPath(color, dataString) {
|
||||
this._s += '<path fill="' + color + '" d="' + dataString + '"/>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rendered image as an SVG string.
|
||||
*/
|
||||
toString() {
|
||||
return this._s + "</svg>";
|
||||
}
|
||||
}
|
||||
45
jdenticon-js/src/renderer/transform.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Jdenticon
|
||||
* https://github.com/dmester/jdenticon
|
||||
* Copyright © Daniel Mester Pirttijärvi
|
||||
*/
|
||||
|
||||
import { Point } from "./point";
|
||||
|
||||
/**
|
||||
* Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself,
|
||||
* but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly.
|
||||
*/
|
||||
export class Transform {
|
||||
/**
|
||||
* @param {number} x The x-coordinate of the upper left corner of the transformed rectangle.
|
||||
* @param {number} y The y-coordinate of the upper left corner of the transformed rectangle.
|
||||
* @param {number} size The size of the transformed rectangle.
|
||||
* @param {number} rotation Rotation specified as 0 = 0 rad, 1 = 0.5π rad, 2 = π rad, 3 = 1.5π rad
|
||||
*/
|
||||
constructor(x, y, size, rotation) {
|
||||
this._x = x;
|
||||
this._y = y;
|
||||
this._size = size;
|
||||
this._rotation = rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the specified point based on the translation and rotation specification for this Transform.
|
||||
* @param {number} x x-coordinate
|
||||
* @param {number} y y-coordinate
|
||||
* @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
|
||||
* @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
|
||||
*/
|
||||
transformIconPoint(x, y, w, h) {
|
||||
const right = this._x + this._size,
|
||||
bottom = this._y + this._size,
|
||||
rotation = this._rotation;
|
||||
return rotation === 1 ? new Point(right - y - (h || 0), this._y + x) :
|
||||
rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
|
||||
rotation === 3 ? new Point(this._x + y, bottom - x - (w || 0)) :
|
||||
new Point(this._x + x, this._y + y);
|
||||
}
|
||||
}
|
||||
|
||||
export const NO_TRANSFORM = new Transform(0, 0, 0, 0);
|
||||
11
jdenticon-js/src/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"types": ["node14"],
|
||||
"lib": ["es6", "dom"]
|
||||
},
|
||||
"include": ["*.js"]
|
||||
}
|
||||
4
jdenticon-js/standalone/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "../dist/jdenticon.min.js",
|
||||
"types": "../types/umd.d.ts"
|
||||
}
|
||||
2
jdenticon-js/test/e2e/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.bundle.js
|
||||
*.bundle.js.map
|
||||
40
jdenticon-js/test/e2e/base-browser-test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const tap = require("tap");
|
||||
const canvasRenderer = require("canvas-renderer");
|
||||
|
||||
export function testBrowser(jdenticon, bundle) {
|
||||
tap.test(bundle, bundleTest => {
|
||||
bundleTest.test("jdenticon.bundle", t => {
|
||||
t.equal(jdenticon.bundle, bundle);
|
||||
t.end();
|
||||
});
|
||||
|
||||
bundleTest.test("jdenticon.version", t => {
|
||||
t.match(jdenticon.version, /^\d+(\.\d+)*$/);
|
||||
t.end();
|
||||
});
|
||||
|
||||
bundleTest.test("jdenticon.configure", t => {
|
||||
t.doesNotThrow(() => jdenticon.configure({ backColor: "#fff" }));
|
||||
t.end();
|
||||
});
|
||||
|
||||
bundleTest.test("jdenticon.drawIcon", t => {
|
||||
t.doesNotThrow(() => jdenticon.drawIcon(canvasRenderer.createCanvas(100, 100).getContext("2d"), "Icon1", 100));
|
||||
t.end();
|
||||
});
|
||||
|
||||
bundleTest.test("jdenticon.toSvg", t => {
|
||||
t.match(jdenticon.toSvg("Icon1", 100), /^<svg/);
|
||||
t.end();
|
||||
});
|
||||
|
||||
bundleTest.test("jdenticon.update*", t => {
|
||||
t.type(jdenticon.update, Function);
|
||||
t.type(jdenticon.updateCanvas, Function);
|
||||
t.type(jdenticon.updateSvg, Function);
|
||||
t.end();
|
||||
});
|
||||
|
||||
bundleTest.end();
|
||||
});
|
||||
}
|
||||
35
jdenticon-js/test/e2e/browser/assets/amd.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jdenticon</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>"Icon0" - AMD - Should be equal</h1>
|
||||
<figure>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAWlBMVEUAAP9aOuYxMYKdne+CXN5AQGLHx+lDQ2TIyOlDQ13NzedHR1ZHR1jW1ufY2OdLS1Dg4OVKSk/g4OWjc9Gjc9FLS02ldNDj4+RLS0yldNDj4+VMTEymddHl5eU2yqFLAAAAG3RSTlMRHy8vPVtbXFxra4iIiIjDw8TE0tPw8PDx8fEmYw1hAAABrUlEQVR42u2ZYWvCQBBE6542UVuT2Kaxnvf//2aR45hrl2mgxyKFfR+HjM9EggP35DiOY0x4fn0OP0PZveyEhLTOFWO6M367ToZ4ZxAe6jrnsKTMckC4n2Nm3tNQ1ylhSYUllFDmWJiFhrrOGBMYSzhEMNBQ1wkh1YQcSqwRErK6pk81fQ67WNORkNU1p1RzyuEx1hxJSOrmd/KA3wScEziXcIpgoqGuM8I1Fa54Ty6xcBEa6jrlcC0X1W/8pXzcnoe6zgnnfLOhDmXKj0VISOtc0596dY10x05oqOuO8xAwD2zrmAeEprqeB4Smup4HhJa6ngeMlrr+KyK01PWfKqOhTuaBYrNtqKuvQhxvn9uG+q8PFY7b7WP7x7qeB9TBLOt1PQ+Yg1nW63oeMAezrNf1POAObuF1Mg+Yg1t4ncMdsFDaHbBYOMwtm3ctwatvYYHDygKHwRKABQ6DIQELHO1DQlvgMBgSsMBhMSRggaN9SBBLdtgMCVjg+K9DAlgOCWA5JIDlkAA2Q4LTPiQ4JkMCrNcdx482/Gij5Whj8qMNP9rwow0/2nAcx7HjC8pg1fBM8ggRAAAAAElFTkSuQmCC" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="AMDIcon0" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update(value) AMD</figcaption>
|
||||
</figure>
|
||||
|
||||
<script>
|
||||
var jdenticon_config = {
|
||||
backColor: "#00f1",
|
||||
replaceMode: "observe"
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/requirejs/2.1.22/require.min.js"></script>
|
||||
<script>
|
||||
|
||||
requirejs(["/node_modules/jdenticon/dist/jdenticon.min.js"], function (jd) {
|
||||
jd.update("#AMDIcon0", "Icon0");
|
||||
});
|
||||
|
||||
</script>
|
||||
<script src="common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
63
jdenticon-js/test/e2e/browser/assets/center.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jdenticon</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>"Icon2" - Should be centered vertically and horizontally</h1>
|
||||
<figure>
|
||||
<canvas data-jdenticon-value="Icon2" width="60" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Icon2-update-vert" width="60" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg data-jdenticon-value="Icon2" width="60" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Icon2-update-vert" width="60" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas data-jdenticon-value="Icon2" width="100" height="60"></canvas>
|
||||
<figcaption><strong>Canvas</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Icon2-update-hori" width="100" height="60"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg data-jdenticon-value="Icon2" width="100" height="60"></svg>
|
||||
<figcaption><strong>SVG</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Icon2-update-hori" width="100" height="60"></svg>
|
||||
<figcaption><strong>SVG</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<script>
|
||||
var jdenticon_config = {
|
||||
backColor: "#00f1",
|
||||
};
|
||||
</script>
|
||||
<script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/require1k@1.0.1/require1k.min.js"></script>
|
||||
<script>
|
||||
jdenticon.update("#canvas-Icon2-update-vert,#svg-Icon2-update-vert", "Icon2");
|
||||
jdenticon.update("#canvas-Icon2-update-hori,#svg-Icon2-update-hori", "Icon2");
|
||||
</script>
|
||||
<script src="common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
jdenticon-js/test/e2e/browser/assets/common.js
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
addEventListener("message", function (ev) {
|
||||
var data = JSON.parse(ev.data);
|
||||
|
||||
if ("scrollHeight" in data) {
|
||||
var iframe = document.getElementsByName(data.name);
|
||||
if (iframe && iframe.length) {
|
||||
iframe[0].style.height = data.scrollHeight + "px";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function postHeight(timeout) {
|
||||
setTimeout(function () {
|
||||
// IE9 does not support passing objects through postMessage
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
scrollHeight: document.body.scrollHeight,
|
||||
name: window.name
|
||||
}), "*");
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
if (window.parent) {
|
||||
postHeight(0);
|
||||
postHeight(1000);
|
||||
addEventListener("resize", postHeight);
|
||||
}
|
||||
66
jdenticon-js/test/e2e/browser/assets/index.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Jdenticon browser test</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="common.js"></script>
|
||||
|
||||
<div class="test-metadata">
|
||||
<div class="jdenticon-info">Unknown Jdenticon version.</div>
|
||||
<div class="browser-info">Unknown browser</div>
|
||||
</div>
|
||||
|
||||
<!-- Tests are run in iframes to allow isolated testing of loaders and configuration -->
|
||||
<iframe name="amd" src="amd.html"></iframe>
|
||||
<iframe name="umd-in-head" src="umd-in-head.html"></iframe>
|
||||
<iframe name="normal" src="normal.html"></iframe>
|
||||
<iframe name="padding-0" src="padding.html?padding=0"></iframe>
|
||||
<iframe name="padding-30" src="padding.html?padding=30"></iframe>
|
||||
<iframe name="center" src="center.html"></iframe>
|
||||
|
||||
<script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
|
||||
<script>
|
||||
|
||||
var BROWSER_REGEX = ["Firefox/", "SamsungBrowser/", "Opera/", "OPR/", "MSIE ", "Trident/", "Edge?/", "Chrome/", "Safari/"];
|
||||
|
||||
function htmlEncode(text) {
|
||||
var div = document.createElement("div");
|
||||
div.innerText = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function detectBrowser() {
|
||||
var html = navigator.userAgent;
|
||||
var hasMatch = false;
|
||||
|
||||
for (var i = 0; !hasMatch && i < BROWSER_REGEX.length; i++) {
|
||||
var tempHtml = html.replace(new RegExp("(\\b" + BROWSER_REGEX[i] + "\\S+)|<|>|&", "g"), function (match) {
|
||||
if (match === "<") return "<";
|
||||
if (match === ">") return ">";
|
||||
if (match === "&") return "&";
|
||||
hasMatch = true;
|
||||
return "<strong>" + htmlEncode(match) + "</strong>";
|
||||
})
|
||||
|
||||
if (hasMatch) {
|
||||
html = tempHtml;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMatch) {
|
||||
html = htmlEncode(html);
|
||||
}
|
||||
|
||||
var browserInfo = document.querySelector(".browser-info");
|
||||
browserInfo.innerHTML = html;
|
||||
}
|
||||
|
||||
var versionInfo = document.querySelector(".jdenticon-info");
|
||||
versionInfo.innerText = "Jdenticon " + jdenticon.version;
|
||||
|
||||
detectBrowser();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
224
jdenticon-js/test/e2e/browser/assets/normal.html
Normal file
@@ -0,0 +1,224 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jdenticon</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>"Icon0" - Should be equal</h1>
|
||||
<figure>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAWlBMVEUAAP9aOuYxMYKdne+CXN5AQGLHx+lDQ2TIyOlDQ13NzedHR1ZHR1jW1ufY2OdLS1Dg4OVKSk/g4OWjc9Gjc9FLS02ldNDj4+RLS0yldNDj4+VMTEymddHl5eU2yqFLAAAAG3RSTlMRHy8vPVtbXFxra4iIiIjDw8TE0tPw8PDx8fEmYw1hAAABrUlEQVR42u2ZYWvCQBBE6542UVuT2Kaxnvf//2aR45hrl2mgxyKFfR+HjM9EggP35DiOY0x4fn0OP0PZveyEhLTOFWO6M367ToZ4ZxAe6jrnsKTMckC4n2Nm3tNQ1ylhSYUllFDmWJiFhrrOGBMYSzhEMNBQ1wkh1YQcSqwRErK6pk81fQ67WNORkNU1p1RzyuEx1hxJSOrmd/KA3wScEziXcIpgoqGuM8I1Fa54Ty6xcBEa6jrlcC0X1W/8pXzcnoe6zgnnfLOhDmXKj0VISOtc0596dY10x05oqOuO8xAwD2zrmAeEprqeB4Smup4HhJa6ngeMlrr+KyK01PWfKqOhTuaBYrNtqKuvQhxvn9uG+q8PFY7b7WP7x7qeB9TBLOt1PQ+Yg1nW63oeMAezrNf1POAObuF1Mg+Yg1t4ncMdsFDaHbBYOMwtm3ctwatvYYHDygKHwRKABQ6DIQELHO1DQlvgMBgSsMBhMSRggaN9SBBLdtgMCVjg+K9DAlgOCWA5JIDlkAA2Q4LTPiQ4JkMCrNcdx482/Gij5Whj8qMNP9rwow0/2nAcx7HjC8pg1fBM8ggRAAAAAElFTkSuQmCC" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas data-jdenticon-value="Icon0" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<h1>"Icon0" - Should be equal to above, but black and no background</h1>
|
||||
<figure>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAMFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaPxwLAAAAD3RSTlMAECAwT1Bgf4C/wM/Q7/B+lp4GAAABKUlEQVR4Ae3ZwUrDUBSEYXtsYhLNPe//tiIlNPCDm5lQCvMtD8pP6GbgfkREXKzuX/eyHqHW/rOW7wjz3g/77DpC7X3Yy3SEtZ9WzxGqz8pyhKnPJssRlj5bxOMLvuTa3wS2ftpMR6jRh1GmI83Hn43ZdqTaHh9b+vE/NS1TmY8Rrx4SROKQIFKHBJE6JIjkIUGkDgkieUgQyUMCbp+8iUOCje/Bijgk2OjeWVGHBBqs6EOCDVb0IcEGK/qQYIMVfUiwwYo+JNhgRceGqYLG5ZXbT8NgxF6xNlDRG1wCrLChDwlW2NCHBCtsyEMCFTbkIYEKG/KQQIUNeUigwsZ7DgnyDwnyDwnyDwnyDwnyDwnyDwny/3tEnjbytJGnjTxt5GkjTxvXioj4BeoMdgqOkiMwAAAAAElFTkSuQmCC" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="explicit-config-update" data-jdenticon-value="Icon0" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update()</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="explicit-config-drawIcon" data-jdenticon-value="Icon0" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> drawIcon()</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="explicit-config-jquery" data-jdenticon-value="Icon0" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> jQuery(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<span id="explicit-config-toSvg"></span>
|
||||
<figcaption><strong>SVG</strong> toSvg(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<h1>"Icon04" - Should be equal but different to the icon above</h1>
|
||||
<figure>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAARVBMVEUAAP9tgO1thu1xiO50i+4xMYJAQGJDQ2RDQ11HR1ZHR1hLS1Cpx+RKSk+oxuSox+SqyeOqyuNLS01LS0xMTExZl8esy+PUIpVsAAAAFHRSTlMRKiotLi9bXGuIiMPDxMXN4uXw8Y2KdJAAAAGXSURBVHgB7JHbioNAEAVPLh11zewmY6///6lLWCTi1MjQkDfrsanyIKODAAcH5+/zx/PTY36cFAZzkOb4CuckgWa3r5tJeGzIQSo0G/3FaOIj5fsbpdZn/yf34iPn9Y1Ss+wL2VQ/ck4boI3+ZpT4yDlvgGa+xiQ+ck4bpHW+ppOER8633OeSu6TB1wyS8Mj5lsuzkJ6X9j/hHFZQan0TzmEFpeRvksRHznmFJJt8YTLxkfPaCkn9tHyuFx85r6+QZMlfJBMfOd9bQcm6oTMJjg05ruxLgZy0+EZ7fv25Kk4g/21Gcf7as2NUhYIwisErePvf7gPbbwrDKKKcdBaHILeYwP+FEvKgS5wLedAlzIE86BLmYh5ECXM458Hhq/r7gfMzPkVN4lx8VKPEuZgHUeL8/f/kk9/EPEgS52IeRIlzMQ+ixLmYB1HiXMyDKHEu0iW/9TJO0j98D4ki6SGRJT0kuqSHRJEsJBYSCwmR65AQuQ4JkeuQEGH+JHt+ZaeNnTZ22vjbaWOnjZ02dtrYaQNeNh9j/APLmwnC0xscngAAAABJRU5ErkJggg==" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas data-jdenticon-value="Icon04" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas data-jdenticon-hash="f97deee25dde01afb80ec1c7e4fae746492ddacb" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> data-jdenticon-hash</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-update-hash" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update(hash)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-update-value" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-jquery-hash" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> jQuery(hash)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-jquery-value" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> jQuery(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-drawIcon-hash" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> drawIcon(hash)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-drawIcon-value" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> drawIcon(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<span id="placeholder-canvas-Jdenticon-dynamic"></span>
|
||||
<figcaption><strong>Canvas</strong> dynamic</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-resize" data-jdenticon-value="Icon04" width="10" height="10"></canvas>
|
||||
<figcaption><strong>Canvas</strong> resize</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<figure>
|
||||
<svg data-jdenticon-value="Icon04" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg data-jdenticon-hash="f97deee25dde01afb80ec1c7e4fae746492ddacb" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> data-jdenticon-hash</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-update-hash" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> update(hash)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-update-value" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-jquery-hash" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> jQuery(hash)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-jquery-value" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> jQuery(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<span id="svg-Jdenticon-toSvg-hash"></span>
|
||||
<figcaption><strong>SVG</strong> toSvg(hash)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<span id="svg-Jdenticon-toSvg-value"></span>
|
||||
<figcaption><strong>SVG</strong> toSvg(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<figure>
|
||||
<span id="placeholder-svg-Jdenticon-dynamic"></span>
|
||||
<figcaption><strong>SVG</strong> dynamic</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-resize" data-jdenticon-value="Icon04" width="10" height="10"></svg>
|
||||
<figcaption><strong>SVG</strong> resize</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<script>
|
||||
var jdenticon_config = {
|
||||
backColor: "#00f1",
|
||||
replaceMode: "observe"
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/jquery/3.2.1/jquery.slim.min.js"></script>
|
||||
<script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/require1k@1.0.1/require1k.min.js"></script>
|
||||
<script>
|
||||
// Explicit config
|
||||
setTimeout(function () {
|
||||
var explicitConfig = {
|
||||
lightness: {
|
||||
color: [0.00, 0.00],
|
||||
grayscale: [0.00, 0.00]
|
||||
},
|
||||
saturation: {
|
||||
color: 0.00,
|
||||
grayscale: 0.00
|
||||
},
|
||||
padding: 0.08
|
||||
};
|
||||
jdenticon.update("#explicit-config-update", null, explicitConfig);
|
||||
var ctx = document.getElementById("explicit-config-drawIcon").getContext("2d");
|
||||
jdenticon.drawIcon(ctx, "Icon0", 100, explicitConfig);
|
||||
document.getElementById("explicit-config-toSvg").innerHTML =
|
||||
jdenticon.toSvg("Icon0", 100, explicitConfig);
|
||||
$("#explicit-config-jquery").jdenticon("Icon0", explicitConfig);
|
||||
});
|
||||
|
||||
// update()
|
||||
jdenticon.update("#canvas-Jdenticon-update-value,#svg-Jdenticon-update-value", "Icon04");
|
||||
jdenticon.update("#canvas-Jdenticon-update-hash,#svg-Jdenticon-update-hash", "f97deee25dde01afb80ec1c7e4fae746492ddacb");
|
||||
|
||||
// drawIcon()
|
||||
var ctx = document.getElementById("canvas-Jdenticon-drawIcon-value").getContext("2d");
|
||||
jdenticon.drawIcon(ctx, "Icon04", 100, 0.08);
|
||||
|
||||
var ctx = document.getElementById("canvas-Jdenticon-drawIcon-hash").getContext("2d");
|
||||
jdenticon.drawIcon(ctx, "f97deee25dde01afb80ec1c7e4fae746492ddacb", 100, 0.08);
|
||||
|
||||
// toSvg()
|
||||
document.getElementById("svg-Jdenticon-toSvg-value").innerHTML =
|
||||
jdenticon.toSvg("Icon04", 100);
|
||||
|
||||
document.getElementById("svg-Jdenticon-toSvg-hash").innerHTML =
|
||||
jdenticon.toSvg("f97deee25dde01afb80ec1c7e4fae746492ddacb", 100);
|
||||
|
||||
// jQuery
|
||||
$("#canvas-Jdenticon-jquery-value,#svg-Jdenticon-jquery-value").jdenticon("Icon04");
|
||||
$("#canvas-Jdenticon-jquery-hash,#svg-Jdenticon-jquery-hash").jdenticon("f97deee25dde01afb80ec1c7e4fae746492ddacb");
|
||||
|
||||
// Dynamic icons
|
||||
setTimeout(function () {
|
||||
// Canvas
|
||||
document.getElementById("placeholder-canvas-Jdenticon-dynamic").innerHTML =
|
||||
'<canvas data-jdenticon-value="Icon04" width="100" height="100"> Error </canvas>';
|
||||
document.getElementById("canvas-Jdenticon-resize").setAttribute("width", "100");
|
||||
document.getElementById("canvas-Jdenticon-resize").setAttribute("height", "100");
|
||||
|
||||
// Svg
|
||||
document.getElementById("placeholder-svg-Jdenticon-dynamic").innerHTML =
|
||||
'<svg data-jdenticon-value="Icon04" width="100" height="100"> Error </svg>';
|
||||
document.getElementById("svg-Jdenticon-resize").setAttribute("width", "100");
|
||||
document.getElementById("svg-Jdenticon-resize").setAttribute("height", "100");
|
||||
}, 1000);
|
||||
</script>
|
||||
<script src="common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
97
jdenticon-js/test/e2e/browser/assets/padding.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jdenticon</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>"Icon04" - Should be equal - <span id="padding-percent"></span>% padding in style</h1>
|
||||
<figure class="padding-0-only">
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAUVBMVEUAAP8fH6JtgPNrgu1vhe5yiO81NXE4OGU/P1RCQlJBQVJCQk1DQ05ERExDQ0xERElEREqoxuSoxuSox+SqyeOqyeJFRUZFRUZGRkZZl8esy+Mq/AT0AAAAGHRSTlMSISorLi8/TnqIiaantbbExMTFzeLm8PFg+1g3AAABrUlEQVR4AazS2W6DMBQG4XEW0yQsTZsEyvs/aJEqy0AzF5b8337SHLGw2vHrSJ156vCYHwfKZymBucIVSWWocEVSGYTi5fZ5u0RwsJTAPwrd9Lcu4GApgR01rynt1eBgKYENhSWVYwEHT2UQ6qb1OjDwVAahOG0XwcBTCYS47lofgIGnBNKVdtdqARQ8dZ/f7w4w7FoDgIKnTs+38DyVP4mnFhIo/yaeWkig9O+SVCIB6DepHhwsJZSBMK5SY8DBU5kEaHJsbHDw1JoECOnF9AEHTe1IAOK1HdprBAdLCWUomaSEatyQVCKB8nmK8/eZGitI/RTvtz07RmEYBqAYev9bt1uXPgIaTDDfYweJYkME36wmmeSXalUCFFItS4ByqhUJUE61IgHKqfb/Ovnb9wClj2mTAKUsaBKgFDhNApRSrUmAOvBPjtzJkdflVGsSoJBqUQIUUi1KgEKqRQlQSLUoIcqnSxYSk7xK0p+w4y5IStwFSYi7IAlx1ySLu8Xd4u6CuHs+Le6CJMRdkIS4SxKiFhLvlWyW3Sy7WXaz7GbZzbKbZTfLbpa9YJb9AArjwlhWWmFfAAAAAElFTkSuQmCC" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="padding-30-only">
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAJ1BMVEUAAP8wMIU4OGc4OGU+PlibteekwuOlwuRFRUZFRUZGRkZZl8esy+M8HoHgAAAACnRSTlMSME1Oa2ump/DxPobHuQAAANNJREFUeAHt1sEKgzAQhGFjklqN7/+8tSV1PCx42D0E+b+LCwO7IAxkwvMBAAAA9XWX+tV973vSPCdNSkNu9D1lOxRNSgNu9D1p+3onTUr9N/qevP3kSZPSmCOLeURpxJXl/5PWpOmSOtVzS1mPzeU6KfWq55aUc7pOSv3qcpPGaYYphtrdDEpd1O5mUOqidp9/R9+m1EPFM48oHf6I2m0eUeqhdptHlDqo3fYRpaOVcYDG278rtPH2ERr//MYb4htvGLLxvOp51fOq51U/PgAAAAAfGl0flQBGOTMAAAAASUVORK5CYII=" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<figure>
|
||||
<canvas data-jdenticon-value="Icon04" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-update-value" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-jquery-value" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> jQuery(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas id="canvas-Jdenticon-drawIcon-value" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> drawIcon(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<figure>
|
||||
<svg data-jdenticon-value="Icon04" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> data-jdenticon-value</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-update-value" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> update(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<svg id="svg-Jdenticon-jquery-value" width="100" height="100"></svg>
|
||||
<figcaption><strong>SVG</strong> jQuery(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<span id="svg-Jdenticon-toSvg-value"></span>
|
||||
<figcaption><strong>SVG</strong> toSvg(value)</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<script>
|
||||
var padding = 0;
|
||||
|
||||
// Load padding from query string
|
||||
var match = /\?padding=(\d+)/.exec(location.href);
|
||||
if (match) {
|
||||
padding = Number(match[1]);
|
||||
}
|
||||
|
||||
document.getElementById("padding-percent").innerHTML = padding;
|
||||
|
||||
var className = document.body.className;
|
||||
document.body.className = (className ? className + " " : "") + "padding-" + padding;
|
||||
|
||||
var jdenticon_config = {
|
||||
padding: padding / 100,
|
||||
backColor: "#00f1",
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/jquery/3.2.1/jquery.slim.min.js"></script>
|
||||
<script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/require1k@1.0.1/require1k.min.js"></script>
|
||||
<script>
|
||||
jdenticon.update("#canvas-Jdenticon-update-value,#svg-Jdenticon-update-value", "Icon04");
|
||||
|
||||
var ctx = document.getElementById("canvas-Jdenticon-drawIcon-value").getContext("2d");
|
||||
jdenticon.drawIcon(ctx, "Icon04", 100);
|
||||
|
||||
document.getElementById("svg-Jdenticon-toSvg-value").innerHTML =
|
||||
jdenticon.toSvg("Icon04", 100);
|
||||
|
||||
$("#canvas-Jdenticon-jquery-value,#svg-Jdenticon-jquery-value").jdenticon("Icon04");
|
||||
</script>
|
||||
<script src="common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
64
jdenticon-js/test/e2e/browser/assets/styles.css
Normal file
@@ -0,0 +1,64 @@
|
||||
html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font: bold 16px Arial;
|
||||
margin: 30px 0 15px;
|
||||
}
|
||||
|
||||
h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.test-metadata {
|
||||
font: 14px Arial;
|
||||
margin: 0 0 1em;
|
||||
min-height: 4em;
|
||||
background: #C1D1EA;
|
||||
padding: 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.jdenticon-info {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
canvas,
|
||||
svg,
|
||||
img {
|
||||
border: 6px solid #444;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
figure {
|
||||
width: 116px;
|
||||
font: 10px Arial;
|
||||
margin: 0 10px 16px 0;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
figure strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.padding-0 .padding-30-only,
|
||||
.padding-30 .padding-0-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin: 0 -20px;
|
||||
}
|
||||
26
jdenticon-js/test/e2e/browser/assets/umd-in-head.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jdenticon</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<script>
|
||||
var jdenticon_config = {
|
||||
backColor: "#00f1"
|
||||
};
|
||||
</script>
|
||||
<script src="/node_modules/jdenticon/dist/jdenticon.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>"Icon0" - UMD in <head> - Should be equal</h1>
|
||||
<figure>
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAWlBMVEUAAP9aOuYxMYKdne+CXN5AQGLHx+lDQ2TIyOlDQ13NzedHR1ZHR1jW1ufY2OdLS1Dg4OVKSk/g4OWjc9Gjc9FLS02ldNDj4+RLS0yldNDj4+VMTEymddHl5eU2yqFLAAAAG3RSTlMRHy8vPVtbXFxra4iIiIjDw8TE0tPw8PDx8fEmYw1hAAABrUlEQVR42u2ZYWvCQBBE6542UVuT2Kaxnvf//2aR45hrl2mgxyKFfR+HjM9EggP35DiOY0x4fn0OP0PZveyEhLTOFWO6M367ToZ4ZxAe6jrnsKTMckC4n2Nm3tNQ1ylhSYUllFDmWJiFhrrOGBMYSzhEMNBQ1wkh1YQcSqwRErK6pk81fQ67WNORkNU1p1RzyuEx1hxJSOrmd/KA3wScEziXcIpgoqGuM8I1Fa54Ty6xcBEa6jrlcC0X1W/8pXzcnoe6zgnnfLOhDmXKj0VISOtc0596dY10x05oqOuO8xAwD2zrmAeEprqeB4Smup4HhJa6ngeMlrr+KyK01PWfKqOhTuaBYrNtqKuvQhxvn9uG+q8PFY7b7WP7x7qeB9TBLOt1PQ+Yg1nW63oeMAezrNf1POAObuF1Mg+Yg1t4ncMdsFDaHbBYOMwtm3ctwatvYYHDygKHwRKABQ6DIQELHO1DQlvgMBgSsMBhMSRggaN9SBBLdtgMCVjg+K9DAlgOCWA5JIDlkAA2Q4LTPiQ4JkMCrNcdx482/Gij5Whj8qMNP9rwow0/2nAcx7HjC8pg1fBM8ggRAAAAAElFTkSuQmCC" width="100" height="100">
|
||||
<figcaption><strong>IMG</strong> static image</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure>
|
||||
<canvas data-jdenticon-value="Icon0" width="100" height="100"></canvas>
|
||||
<figcaption><strong>Canvas</strong> UMD in <head></figcaption>
|
||||
</figure>
|
||||
<script src="common.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
jdenticon-js/test/e2e/browser/expected/macos-chrome.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/macos-firefox.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/macos-safari.png
Normal file
|
After Width: | Height: | Size: 389 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-bs1.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-bs2.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-bs3.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-chrome.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-edge.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-firefox.png
Normal file
|
After Width: | Height: | Size: 303 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-ie10.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-ie11.png
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
jdenticon-js/test/e2e/browser/expected/win-ie9.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
45
jdenticon-js/test/e2e/browser/screenshooter.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const { PNG } = require("pngjs");
|
||||
|
||||
async function screenshot(driver) {
|
||||
var dimensions = await driver.executeScript(`return {
|
||||
scrollWidth: document.body.offsetWidth,
|
||||
scrollHeight: document.body.offsetHeight,
|
||||
innerWidth: window.innerWidth || document.documentElement.clientWidth,
|
||||
innerHeight: window.innerHeight || document.documentElement.clientHeight
|
||||
}`);
|
||||
|
||||
const combinedImage = new PNG({
|
||||
width: dimensions.scrollWidth,
|
||||
height: dimensions.scrollHeight
|
||||
});
|
||||
|
||||
const xnum = Math.ceil(dimensions.scrollWidth / dimensions.innerWidth);
|
||||
const ynum = Math.ceil(dimensions.scrollHeight / dimensions.innerHeight);
|
||||
|
||||
for (let x = 0; x < xnum; x++) {
|
||||
for (let y = 0; y < ynum; y++) {
|
||||
|
||||
var scrollpos = await driver.executeScript(`
|
||||
window.scrollTo(${x * dimensions.innerWidth}, ${y * dimensions.innerHeight});
|
||||
return { x: window.scrollX || window.pageXOffset, y: window.scrollY || window.pageYOffset }`)
|
||||
|
||||
// Delay for Safari
|
||||
await driver.sleep(500);
|
||||
|
||||
const datauri = await driver.takeScreenshot();
|
||||
const image = PNG.sync.read(Buffer.from(datauri, "base64"));
|
||||
|
||||
PNG.bitblt(image, combinedImage,
|
||||
0, 0,
|
||||
Math.min(image.width, combinedImage.width - scrollpos.x),
|
||||
Math.min(image.height, combinedImage.height - scrollpos.y),
|
||||
scrollpos.x,
|
||||
scrollpos.y);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return PNG.sync.write(combinedImage);
|
||||
}
|
||||
|
||||
module.exports = screenshot;
|
||||
187
jdenticon-js/test/e2e/browser/test.js
Normal file
@@ -0,0 +1,187 @@
|
||||
const express = require("express");
|
||||
const fs = require("fs");
|
||||
const tap = require("tap");
|
||||
const webdriver = require("selenium-webdriver");
|
||||
const screenshot = require("./screenshooter");
|
||||
const BlinkDiff = require("blink-diff");
|
||||
const path = require("path");
|
||||
|
||||
// Command line arguments examples:
|
||||
// node test.js win ie11,chrome
|
||||
// node test.js macos safari,chrome,firefox
|
||||
|
||||
const environmentId = process.argv[2] || "";
|
||||
const enabledBrowsers = (process.argv[3] || "").split(/[,;]/g).filter(name => name);
|
||||
|
||||
if (!enabledBrowsers.length) {
|
||||
throw new Error("Expected browser names");
|
||||
}
|
||||
|
||||
const screenshotDir = process.env.BROWSER_SCREENSHOT_DIR || path.join(__dirname, "artifacts/screenshots");
|
||||
const diffDir = process.env.BROWSER_DIFF_DIR || path.join(__dirname, "artifacts/diffs");
|
||||
const expectedDir = process.env.BROWSER_EXPECTED_DIR || path.join(__dirname, "expected");
|
||||
|
||||
// fs.mkdirSync(_ , { recursive: true }) did not work on GitHub Actions using Node v12.18.2 (Windows and macOS).
|
||||
// Worked fine locally however. Replacing it with a custom recursive implementation.
|
||||
// Ignored GitHub issue for tracking any status:
|
||||
// https://github.com/nodejs/node/issues/27293
|
||||
function mkdirRecursive(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
const parent = path.dirname(dirPath)
|
||||
if (parent && parent !== dirPath) {
|
||||
mkdirRecursive(parent);
|
||||
}
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
mkdirRecursive(screenshotDir);
|
||||
mkdirRecursive(diffDir);
|
||||
|
||||
const BROWSER_DEFINITIONS = [
|
||||
{
|
||||
name: "ie11",
|
||||
uaCompatible: "IE=Edge",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.INTERNET_EXPLORER,
|
||||
"ie.ensureCleanSession": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ie10",
|
||||
uaCompatible: "IE=10",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.INTERNET_EXPLORER,
|
||||
"ie.ensureCleanSession": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ie9",
|
||||
uaCompatible: "IE=9",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.INTERNET_EXPLORER,
|
||||
"ie.ensureCleanSession": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "firefox",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.FIREFOX,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "chrome",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.CHROME,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "edge",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.EDGE,
|
||||
"ms:edgeChromium": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "safari",
|
||||
capabilities: {
|
||||
"browserName": webdriver.Browser.SAFARI,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
async function serve(root, options, asyncCallback) {
|
||||
const app = express();
|
||||
|
||||
app.use(express.static(root, options));
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const listener = app.listen(async () => {
|
||||
try {
|
||||
await asyncCallback(listener);
|
||||
resolve();
|
||||
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
|
||||
} finally {
|
||||
listener.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function testBrowser(browserName) {
|
||||
const browser = BROWSER_DEFINITIONS.find(x => x.name === browserName);
|
||||
await tap.test(browserName, async t => {
|
||||
if (!browser) {
|
||||
t.fail(`Could not find a browser with the name ${browserName}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await serve(
|
||||
path.join(__dirname, "../../"),
|
||||
{
|
||||
"index": ["index.html"],
|
||||
setHeaders: resp => {
|
||||
// Prevent stale files
|
||||
resp.setHeader("Cache-Control", "no-store");
|
||||
|
||||
if (browser.uaCompatible) {
|
||||
resp.setHeader("X-UA-Compatible", browser.uaCompatible);
|
||||
}
|
||||
}
|
||||
},
|
||||
async listener => {
|
||||
const url = "http://localhost:" + listener.address().port + "/e2e/browser/assets/";
|
||||
|
||||
console.log(`Screenshot in ${browserName}`);
|
||||
console.log(url);
|
||||
|
||||
const driver = await new webdriver.Builder()
|
||||
.withCapabilities(browser.capabilities)
|
||||
.build();
|
||||
|
||||
await driver.manage().window().setRect({ width: 1000, height: 2000 });
|
||||
|
||||
const documentInitialised = () => driver.executeScript("return true");
|
||||
|
||||
try {
|
||||
await driver.get(url);
|
||||
await driver.wait(() => documentInitialised(), 10000);
|
||||
await driver.sleep(2500);
|
||||
|
||||
const screenshotBuffer = await screenshot(driver);
|
||||
fs.writeFileSync(path.join(screenshotDir, `${environmentId}-${browserName}.png`), screenshotBuffer);
|
||||
|
||||
} finally {
|
||||
await driver.quit();
|
||||
}
|
||||
|
||||
var diff = new BlinkDiff({
|
||||
imageAPath: path.join(expectedDir, `${environmentId}-${browserName}.png`),
|
||||
imageBPath: path.join(screenshotDir, `${environmentId}-${browserName}.png`),
|
||||
|
||||
thresholdType: BlinkDiff.THRESHOLD_PIXEL,
|
||||
threshold: 1000,
|
||||
|
||||
imageOutputPath: path.join(diffDir, `${environmentId}-${browserName}.png`),
|
||||
|
||||
// Ignore test metadata area containing browser versions etc.
|
||||
blockOut: [{ x: 0, y: 0, width: 20000, height: 100 }],
|
||||
});
|
||||
|
||||
const diffResult = await diff.runWithPromise();
|
||||
t.ok(diff.hasPassed(diffResult.code), `Found ${diffResult.differences} differences.`);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function testBrowsers(enabledBrowsers) {
|
||||
for (var i = 0; i < enabledBrowsers.length; i++) {
|
||||
await testBrowser(enabledBrowsers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
testBrowsers(enabledBrowsers);
|
||||