Skip to content

Commit

Permalink
feat(renderers): add support form custom h function
Browse files Browse the repository at this point in the history
  • Loading branch information
nmanumr committed Jul 19, 2021
1 parent 7d38c25 commit 6518595
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 19 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@
},
"homepage": "https://nmanumr.github.io/mazes101/",
"devDependencies": {
"preact": "^10.5.14",
"rollup": "^2.47.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.30.0",
"semantic-release": "^17.4.2",
"ts-transformer-keys": "^0.4.3",
"tslib": "^2.2.0",
"tsutils": "^3.21.0",
"ttypescript": "^1.5.12",
"typescript": "^4.2.4",
"semantic-release": "^17.4.2"
"typescript": "^4.2.4"
},
"dependencies": {}
}
122 changes: 122 additions & 0 deletions src/h.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {JSX as JSXInternal} from "preact"

type Child =
| HTMLElement
| Text
| string
| number

type Attributes =
& JSXInternal.HTMLAttributes
& JSXInternal.SVGAttributes
& Record<string, any>;

// CAUTION: These are not all the svg tags but some of the svg tags
// that can be used in the scope of this project
const svgTags = ['svg', 'path', 'circle', 'line', 'rect'];
const attrMap = {
'className': 'class',
}

function normalizeAttr(attr: string) {
if (attrMap[attr]) attr = attrMap[attr];

return attr.split('').map((letter, idx) => {
return letter.toUpperCase() === letter
? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`
: letter;
}).join('');
}


/**
* Append a child node to an element
*
* @param el - Element
* @param child - Child node(s)
*/
export function appendChild(el: HTMLElement | SVGElement, child: Child | Child[]): void {
/* Handle primitive types (including raw HTML) */
if (typeof child === "string" || typeof child === "number") {
el.innerHTML += child.toString()

/* Handle nodes */
} else if (child instanceof Node) {
el.appendChild(child)

/* Handle nested children */
} else if (Array.isArray(child)) {
for (const node of child)
appendChild(el, node)
}
}

/**
* JSX factory that renders DOMElement
*/
export function DomH(
tag: string, attributes: Attributes | null, ...children: Child[]
): HTMLElement | SVGElement {
let el;

/* Handle svg element */
if (svgTags.includes(tag)) {
el = document.createElementNS("http://www.w3.org/2000/svg", tag);

/* Handle normal html element */
} else {
el = document.createElement(tag);
}

/* Set attributes, if any */
if (attributes) {
for (const attr of Object.keys(attributes)) {
if (typeof attributes[attr] !== "boolean") {
el.setAttribute(normalizeAttr(attr), attributes[attr]);
} else if (attributes[attr]) {
el.setAttribute(normalizeAttr(attr), "");
}
}
}

/* Append child nodes */
for (const child of children) {
appendChild(el, child)
}

/* Return element */
return el
}

/**
* JSX factory that renders HTML string
*/
export function StrH(tag: string, attributes: Attributes | null, ...children: Child[]): string {
let attrStr = Object.entries(attributes).map(([key, val]) => {
if (typeof val === 'boolean') {
return normalizeAttr(key);
}
return `${normalizeAttr(key)}="${val}"`;
}).join(' ');

let childStr = children.map((child) => child.toString()).join('');

if (childStr) {
if (attrStr) {
return `<${tag} ${attrStr}>${childStr}</${tag}>`;
}
return `<${tag}>${childStr}</${tag}>`;
} else if (attrStr) {
return `<${tag} ${attrStr} />`;
}

return `<${tag} />`;
}

/* This override is necessary for types to work */
export declare namespace h {
namespace JSX {
type Element = HTMLElement
type IntrinsicElements = JSXInternal.IntrinsicElements
}
}
27 changes: 18 additions & 9 deletions src/renderers/circularSvg.ts → src/renderers/circularSvg.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import {CircularBoard, Direction, getRows} from "../boards/circular.js";
import {BoardType} from "../base.js";
import {StrH as globalH} from '../h';

interface RendererOptions {
interface RendererOptions<T> {
cellSize: number;
lineWidth: number;
h?: (tag: string, attributes: Record<string, string>, ...children: Array<any>) => T;
}

const defaultOptions: RendererOptions = {
const defaultOptions: RendererOptions<string> = {
cellSize: 30,
lineWidth: 2,
h: globalH,
}

export function render(board: CircularBoard, options: Partial<RendererOptions> = {}) {
options = {...defaultOptions, ...options};
export function render<T = string>(board: CircularBoard, options: Partial<RendererOptions<T>> = {}): T {
options = {...defaultOptions, ...options} as RendererOptions<T>;

const innerRadius = board.size.innerRadius;
const radiusOffset = (1 - innerRadius) * options.cellSize * 0.75;
const radius = options.cellSize * (board.size.radius + innerRadius) * 2 + options.lineWidth + radiusOffset;
const canvasSize = radius + (board.size.radius + innerRadius) * 2;
const radius = options.cellSize * (board.size.radius - board.size.innerRadius) + radiusOffset + innerRadius * options.cellSize;

const canvasSize = radius * 2 + options.lineWidth;
const center = canvasSize / 2;

let rows = getRows(board);
Expand Down Expand Up @@ -54,9 +59,13 @@ export function render(board: CircularBoard, options: Partial<RendererOptions> =
}
}

return `<svg stroke="currentColor" fill="none" width="${canvasSize}" height="${canvasSize}" viewBox="0 0 ${canvasSize} ${canvasSize}">
<path d="${path}" stroke-width="${options.lineWidth}" stroke-linecap="round"/>
</svg>`;
const h = options.h;
return (
<svg stroke="currentColor" fill="none" width={canvasSize} height={canvasSize}
viewbox={`0 0 ${canvasSize} ${canvasSize}`}>
<path d={path} strokeWidth={options.lineWidth} strokeLinecap="round"/>
</svg>
);
}

export const _supported_boards = [BoardType.Circular];
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import {Direction, RectangularBoard, toPosition} from "../boards/rectangular.js";
import {BoardType, isEnabled} from "../base.js";
import {StrH as globalH} from "../h";

interface RendererOptions {
interface RendererOptions<T> {
cellSize: number;
lineWidth: number;
renderDisabled: boolean;
h?: (tag: string, attributes: Record<string, string>, ...children: Array<any>) => T;
}

const defaultOptions: RendererOptions = {
const defaultOptions: RendererOptions<string> = {
cellSize: 30,
lineWidth: 2,
renderDisabled: false,
h: globalH,
}

export function render(board: RectangularBoard, options: Partial<RendererOptions> = {}) {
options = {...defaultOptions, ...options};
export function render<T = string>(board: RectangularBoard, options: Partial<RendererOptions<T>> = {}): T {
options = {...defaultOptions, ...options} as RendererOptions<T>;

const width = options.cellSize * (board.size.width + 2) + options.lineWidth;
const height = options.cellSize * (board.size.height + 2) + options.lineWidth;
Expand Down Expand Up @@ -44,10 +49,13 @@ export function render(board: RectangularBoard, options: Partial<RendererOptions
return acc;
}, '');

return `<svg stroke="currentColor" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<path d="${p2}" fill="#ddd" stroke-width="${0}" stroke-linecap="round"/>
<path d="${path}" stroke-width="${options.lineWidth}" stroke-linecap="round"/>
</svg>`;
const h = options.h;
return (
<svg stroke="currentColor" width={width} height={height} viewbox={`0 0 ${width} ${height}`}>
{options.renderDisabled && <path d={p2} className={'disabledPath'}/>}
<path d={path} strokeWidth={options.lineWidth} strokeLinecap="round"/>
</svg>
);
}

export const _supported_boards = [BoardType.Rectangular];
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"module": "ESNext",
"moduleResolution": "node",
"target": "ES2019",
"jsx": "react",
"jsxFactory": "h",
"plugins": [
{ "transform": "ts-transformer-keys/transformer" }
],
Expand Down

0 comments on commit 6518595

Please sign in to comment.