1
0
Files
VR-Web-Player/src/vr180player/dom/icons.ts
Aiden b674df1555
Some checks failed
Test / test (push) Has been cancelled
Updated
2026-06-11 09:12:17 +10:00

205 lines
5.4 KiB
TypeScript

export type LucideIconName =
| 'circle-play'
| 'play'
| 'pause'
| 'maximize'
| 'arrow-left'
| 'chevron-left'
| 'chevron-right'
| 'rotate-ccw'
| 'rotate-cw'
| 'repeat'
| 'volume-2'
| 'volume-x'
| 'log-out'
| 'x';
type IconAttrs = Record<string, string>;
type IconNode = readonly [tagName: string, attrs: IconAttrs];
const SVG_NS = 'http://www.w3.org/2000/svg';
const ICONS: Record<LucideIconName, readonly IconNode[]> = {
'circle-play': [
['circle', { cx: '12', cy: '12', r: '10' }],
['polygon', { points: '10 8 16 12 10 16 10 8' }]
],
play: [
['polygon', { points: '6 3 20 12 6 21 6 3' }]
],
pause: [
['rect', { x: '14', y: '4', width: '4', height: '16', rx: '1' }],
['rect', { x: '6', y: '4', width: '4', height: '16', rx: '1' }]
],
maximize: [
['path', { d: 'M8 3H5a2 2 0 0 0-2 2v3' }],
['path', { d: 'M21 8V5a2 2 0 0 0-2-2h-3' }],
['path', { d: 'M3 16v3a2 2 0 0 0 2 2h3' }],
['path', { d: 'M16 21h3a2 2 0 0 0 2-2v-3' }]
],
'arrow-left': [
['path', { d: 'm12 19-7-7 7-7' }],
['path', { d: 'M19 12H5' }]
],
'chevron-left': [
['path', { d: 'm15 18-6-6 6-6' }]
],
'chevron-right': [
['path', { d: 'm9 18 6-6-6-6' }]
],
'rotate-ccw': [
['path', { d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }],
['path', { d: 'M3 3v5h5' }]
],
'rotate-cw': [
['path', { d: 'M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8' }],
['path', { d: 'M21 3v5h-5' }]
],
repeat: [
['path', { d: 'm17 2 4 4-4 4' }],
['path', { d: 'M3 11v-1a4 4 0 0 1 4-4h14' }],
['path', { d: 'm7 22-4-4 4-4' }],
['path', { d: 'M21 13v1a4 4 0 0 1-4 4H3' }]
],
'volume-2': [
['path', { d: 'M11 4.702a1 1 0 0 0-1.664-.747L5.5 7.5H4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h1.5l3.836 3.545A1 1 0 0 0 11 19.298z' }],
['path', { d: 'M16 9a5 5 0 0 1 0 6' }],
['path', { d: 'M19.364 18.364a9 9 0 0 0 0-12.728' }]
],
'volume-x': [
['path', { d: 'M11 4.702a1 1 0 0 0-1.664-.747L5.5 7.5H4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h1.5l3.836 3.545A1 1 0 0 0 11 19.298z' }],
['line', { x1: '22', y1: '9', x2: '16', y2: '15' }],
['line', { x1: '16', y1: '9', x2: '22', y2: '15' }]
],
'log-out': [
['path', { d: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4' }],
['polyline', { points: '16 17 21 12 16 7' }],
['line', { x1: '21', y1: '12', x2: '9', y2: '12' }]
],
x: [
['path', { d: 'M18 6 6 18' }],
['path', { d: 'm6 6 12 12' }]
]
};
export function createLucideIcon(name: LucideIconName, className = 'vrwp-icon'): SVGSVGElement {
const svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('class', className);
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '2');
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('focusable', 'false');
for (const [tagName, attrs] of ICONS[name]) {
const node = document.createElementNS(SVG_NS, tagName);
for (const [key, value] of Object.entries(attrs)) {
node.setAttribute(key, value);
}
svg.appendChild(node);
}
return svg;
}
export function setLucideIcon(target: HTMLElement, name: LucideIconName): void {
const existingIcon = target.querySelector('.vrwp-icon');
if (existingIcon) {
existingIcon.replaceWith(createLucideIcon(name));
return;
}
target.prepend(createLucideIcon(name));
}
export function drawLucideIcon(
ctx: CanvasRenderingContext2D,
name: LucideIconName,
x: number,
y: number,
size: number,
color = '#ffffff',
strokeWidth = 2
): void {
ctx.save();
ctx.translate(x, y);
ctx.scale(size / 24, size / 24);
ctx.strokeStyle = color;
ctx.lineWidth = strokeWidth;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
for (const [tagName, attrs] of ICONS[name]) {
drawIconNode(ctx, tagName, attrs);
}
ctx.restore();
}
function drawIconNode(ctx: CanvasRenderingContext2D, tagName: string, attrs: IconAttrs): void {
switch (tagName) {
case 'path':
ctx.stroke(new Path2D(attrs.d));
break;
case 'line':
ctx.beginPath();
ctx.moveTo(Number(attrs.x1), Number(attrs.y1));
ctx.lineTo(Number(attrs.x2), Number(attrs.y2));
ctx.stroke();
break;
case 'polyline':
case 'polygon':
drawPoints(ctx, attrs.points, tagName === 'polygon');
break;
case 'rect':
drawRect(ctx, attrs);
break;
case 'circle':
ctx.beginPath();
ctx.arc(Number(attrs.cx), Number(attrs.cy), Number(attrs.r), 0, Math.PI * 2);
ctx.stroke();
break;
}
}
function drawPoints(ctx: CanvasRenderingContext2D, pointsAttr: string, closePath: boolean): void {
const coordinates = pointsAttr.trim().split(/[\s,]+/).map(Number);
const points = [];
for (let index = 0; index < coordinates.length - 1; index += 2) {
const x = coordinates[index];
const y = coordinates[index + 1];
if (Number.isFinite(x) && Number.isFinite(y)) {
points.push([x, y]);
}
}
if (points.length === 0) return;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (const [x, y] of points.slice(1)) {
ctx.lineTo(x, y);
}
if (closePath) {
ctx.closePath();
}
ctx.stroke();
}
function drawRect(ctx: CanvasRenderingContext2D, attrs: IconAttrs): void {
const x = Number(attrs.x);
const y = Number(attrs.y);
const width = Number(attrs.width);
const height = Number(attrs.height);
const radius = Number(attrs.rx || 0);
ctx.beginPath();
if (radius > 0 && typeof ctx.roundRect === 'function') {
ctx.roundRect(x, y, width, height, radius);
} else {
ctx.rect(x, y, width, height);
}
ctx.stroke();
}