Some progress on porting frontend
Some checks failed
/ docker (push) Has been cancelled

This commit is contained in:
Melody Becker 2025-07-10 20:23:59 +02:00
parent 59373c6380
commit 6515bda730
Signed by: mstar
SSH key fingerprint: SHA256:vkXfS9FG2pVNVfvDrzd1VW9n8VJzqqdKQGljxxX8uK8
18 changed files with 728 additions and 39 deletions

View file

@ -0,0 +1,492 @@
class Crusher {
hash(s: any): number {
return String(s).split("").reduce(function(a, b) {
a = ((a << 5) - a) + b.charCodeAt(0);
return a & a
}, 0);
}
isDOMElement(o:any): boolean {
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement :
o && typeof o === "object" && true && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
}
enum BlockType {
ONE = 1,
TWO = 2,
THREE = 3,
}
class Block {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
type: BlockType;
primary: string;
accent: string;
cellSize: number;
margin: number;
scale: number;
pos: Position;
hash: number;
constructor(
c: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
type: BlockType,
primary: string,
accent: string,
hash: number,
cellSize: number,
margin: number,
scale: number,
pos: Position,
) {
this.canvas = c;
this.context = ctx;
this.type = type;
this.primary = primary;
this.accent = accent;
this.hash = hash;
this.cellSize = cellSize;
this.margin = margin;
this.scale = scale;
this.pos = pos;
}
offset() {
this.context.save();
this.context.translate(0.6 * this.scale, -0.6 * this.scale);
}
resetOffset() {
this.context.restore();
}
makePath(hash: number, offset: number) {
const mod = Math.abs(hash + offset) % 4;
switch(mod) {
case 0:
// top
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y);
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize);
this.context.closePath();
break;
case 1:
// right
this.context.beginPath();
this.context.moveTo(this.pos.x + this.cellSize, this.pos.y);
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize);
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
this.context.closePath();
break;
case 2:
// bottom
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize);
this.context.closePath();
break;
case 3:
// left
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y);
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
this.context.closePath();
break;
default:
// top
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x + this.cellSize, this.pos.y);
this.context.lineTo(this.pos.x, this.pos.y + this.cellSize);
this.context.closePath();
}
}
draw() {
this.offset();
if (this.type === BlockType.ONE) {
this.makePath(this.hash, this.hash % 3);
this.context.fillStyle = this.primary;
this.context.fill();
this.makePath(this.hash, this.hash % 5);
this.context.fillStyle = this.accent;
this.context.fill();
} else if (this.type === BlockType.TWO) {
this.makePath(this.hash, this.hash % 4);
this.context.fillStyle = this.accent;
this.context.fill();
this.makePath(this.hash, this.hash % 3);
this.context.fillStyle = this.primary;
this.context.fill();
} else {
this.makePath(this.hash, this.hash % 7);
this.context.fillStyle = this.accent;
this.context.fill();
this.makePath(this.hash, this.hash % 8);
this.context.fillStyle = this.primary;
this.context.fill();
}
this.resetOffset();
}
}
class Position {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Shape {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
hash: number;
primary: string;
accent: string;
pos: Position;
scale: number;
cellSize: number;
strokeColor: string;
constructor(
c: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
hash: number,
primary: string,
accent: string,
pos: Position,
scale: number,
cellSize: number,
strokeColor: string
) {
this.canvas = c;
this.context = ctx;
this.hash = hash;
this.primary = primary;
this.accent = accent;
this.pos = pos;
this.scale = scale;
this.cellSize = cellSize;
this.strokeColor = strokeColor;
}
getColor() {
return [this.primary, this.accent][Math.abs(this.hash % 2)];
}
makePath() {
const mod = Math.abs(this.hash + 1) % 4;
switch(mod) {
case 0:
// square
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y);
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y - (this.cellSize / 2));
this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize / 2));
this.context.closePath();
break;
case 1:
//circle
this.context.beginPath();
this.context.arc(
this.pos.x + (this.cellSize / Math.PI) - 5,
this.pos.y - (this.cellSize / Math.PI) + 5,
this.cellSize / 3,
0,
Math.PI * 2,
true
);
break;
case 2:
// triangle
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x + (this.cellSize * 0.65), this.pos.y);
this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize * 0.65));
this.context.closePath();
break;
case 3:
// oval
this.context.beginPath();
this.context.moveTo(this.pos.x - (this.cellSize * 0.2), this.pos.y + (this.cellSize * 0.2));
this.context.quadraticCurveTo(this.pos.x + (this.cellSize * 0.4), this.pos.y, this.pos.x + (this.cellSize * 0.5), this.pos.y - (this.cellSize * 0.5));
this.context.moveTo(this.pos.x + (this.cellSize * 0.5), this.pos.y - (this.cellSize * 0.5));
this.context.quadraticCurveTo(this.pos.x , this.pos.y - (this.cellSize * 0.4), this.pos.x - (this.cellSize * 0.2), this.pos.y + (this.cellSize * 0.2));
break;
default:
//square
this.context.beginPath();
this.context.moveTo(this.pos.x, this.pos.y);
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y);
this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y - (this.cellSize / 2));
this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize / 2));
this.context.closePath();
}
}
draw(hasStroke: boolean, strokeWeight: number) {
const color = this.getColor();
this.context.globalCompositeOperation = "source-over";
this.makePath();
this.context.fillStyle = color;
this.context.strokeStyle = this.strokeColor;
this.context.lineWidth = this.scale * ((4/5 * strokeWeight) / this.canvas.width);
this.context.lineJoin = "round";
this.context.lineCap = "round";
this.context.fill();
if (hasStroke) {
this.context.stroke();
}
}
}
/**
* @name IdentiHeart
* @author Schlipak
* @copyright Apache license 2015 Guillaume de Matos
*/
export class Identiheart {
canvas: HTMLCanvasElement
context: CanvasRenderingContext2D
margin: number
crusher: Crusher
palette = [
'#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3',
'#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39',
'#FFEB3B', '#FFC107', '#FF9800', '#FF5722', '#795548', '#607D8B'
];
primary: string = "#F44336";
accent: string = "#E91E63";
scale: number;
cellSize: number;
hash: number = 0;
blocks: Block[] = [];
shape?: Shape;
hasStroke: boolean = true;
strokeWeight = 500;
strokeColor = "#000000";
compositeOperation: GlobalCompositeOperation = 'multiply';
constructor(c: HTMLCanvasElement, margin?: number, scale?: number, ctx?: CanvasRenderingContext2D) {
this.canvas = c;
this.context = ctx ?? c.getContext("2d")!;
this.margin = margin || 5;
this.scale = scale || 20;
this.crusher = new Crusher();
this.cellSize = (this.canvas.width / 2) - (this.margin * this.scale);
}
setUsername(username: string) {
this.hash = this.crusher.hash(username);
return this;
}
setPalette(palette:string[]) {
if (palette.length < 2) {
throw new Error('Palette must be more than two elements long');
}
this.palette = palette;
return this;
}
setHasStroke(b: boolean) {
this.hasStroke = b;
return this;
}
setStrokeWeight(weight: number) {
this.strokeWeight = weight;
return this;
}
setStrokeColor(color: string) {
this.strokeColor = color;
return this;
}
setCompositeOperation(operation: GlobalCompositeOperation) {
this.compositeOperation = operation;
return this;
}
setCanvas(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.context = canvas.getContext('2d')!;
return this;
}
init() {
// Purge the block array
this.blocks = new Array<Block>();
// Purge the shape
this.shape = undefined;
// Generate colors
const crusher = new Crusher();
const subHash = crusher.hash(this.hash);
this.accent = this.palette[Math.abs(subHash % this.palette.length)];
// Clear the canvas
this.context.globalCompositeOperation = "source-over";
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.globalCompositeOperation = this.compositeOperation;
return this;
}
draw() {
this.init();
// Rotate the canvas -45deg
this.context.save();
this.context.translate(this.canvas.width/2, this.canvas.height/2);
this.context.rotate(- Math.PI / 4);
this.context.translate(-this.canvas.width/2, -this.canvas.height/2);
this.generateBlocks();
this.drawBlocks();
if (this.hasStroke) {
this.drawOutline();
}
this.shape = new Shape(this.canvas, this.context, this.hash, this.primary, this.accent, {
x: (this.margin * this.scale) + 1.5 * this.cellSize,
y: (this.margin * this.scale) + 0.5 * this.cellSize
}, this.scale, this.cellSize, this.strokeColor);
this.shape.draw(this.hasStroke, this.strokeWeight);
// Restore the original matrix
this.context.restore();
return this;
}
offset() {
this.context.save();
this.context.translate(0.6 * this.scale, - 0.6 * this.scale);
}
resetOffset() {
this.context.restore();
}
drawOutline() {
this.offset();
this.context.globalCompositeOperation = "source-over";
// Outer lines
this.context.beginPath();
this.context.moveTo(this.margin * this.scale, this.margin * this.scale);
this.context.lineTo(this.margin * this.scale, this.canvas.height - (this.margin * this.scale));
this.context.lineTo(this.canvas.width - (this.margin * this.scale), this.canvas.height - (this.margin * this.scale));
this.context.lineTo(this.canvas.width - (this.margin * this.scale), this.canvas.height / 2);
this.context.lineTo(this.canvas.width / 2, this.canvas.height / 2);
this.context.lineTo(this.canvas.width / 2, this.margin * this.scale);
this.context.closePath();
this.context.strokeStyle = this.strokeColor;
this.context.lineWidth = this.scale * (this.strokeWeight / this.canvas.width);
this.context.lineJoin = "round";
this.context.lineCap = "round";
this.context.stroke();
// Inner lines
this.context.beginPath();
this.context.moveTo(this.canvas.width / 2, this.canvas.height / 2);
this.context.lineTo(this.margin * this.scale, this.canvas.height / 2);
this.context.moveTo(this.canvas.width / 2, this.canvas.height / 2);
this.context.lineTo(this.canvas.width / 2, this.canvas.height - (this.margin * this.scale));
this.context.stroke();
this.resetOffset();
this.context.globalCompositeOperation = this.compositeOperation;
}
generateBlocks() {
/*
c: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
type: BlockType,
primary: string,
accent: string,
hash: number,
cellSize: number,
margin: number,
scale: number,
pos: Position,
* */
let b1 = new Block(
this.canvas,
this.context,
BlockType.ONE,
this.primary,
this.accent,
this.hash,
this.cellSize,
this.margin,
this.scale,
new Position(this.margin * this.scale,this.margin * this.scale));
this.blocks.push(b1);
let b2 = new Block(
this.canvas,
this.context,
BlockType.TWO,
this.primary, this.accent,
this.hash,
this.cellSize,
this.margin,
this.scale,
new Position(this.margin * this.scale,this.canvas.height / 2)
);
this.blocks.push(b2);
let b3 = new Block(
this.canvas,
this.context,
BlockType.THREE,
this.primary,
this.accent,
this.hash,
this.cellSize,
this.margin,
this.scale,
new Position(this.canvas.width / 2, this.canvas.height / 2)
);
this.blocks.push(b3);
}
drawBlocks() {
if (this.blocks.length == 0) {
return false;
}
for (var i = 0; i < this.blocks.length; i++) {
this.blocks[i].draw();
}
}
}