Skip to content

Instantly share code, notes, and snippets.

@kenwebb
Last active September 27, 2023 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenwebb/62ee0cfeb3584c95dae72d4960fcb7e0 to your computer and use it in GitHub Desktop.
Save kenwebb/62ee0cfeb3584c95dae72d4960fcb7e0 to your computer and use it in GitHub Desktop.
Transform ReactJS to Xholon

Transform ReactJS to Xholon

In this Xholon workbook I explore how to transform ReactJS JSX to Xholon format. The gist includes several additional Javascript files.

TODO

Provide a sustantial example of a Xholon implementation that matches an existing reactjs app.

For example: https://react.dev/learn/tutorial-tic-tac-toe

/**
ChatGPT dialog
User
Another correction. I want to use recast, acorn, and acorn-jsx, to transform something like <Thumbnail video={video} /> into something like <Thumbnail video="${video}" />. Provide Javascript code.
ChatGPT
To transform JSX using Recast, Acorn, and Acorn-JSX into the desired format ("<Thumbnail video="${video}" />"), you can follow these steps:
Install the necessary dependencies: npm install recast acorn acorn-jsx
Create a JavaScript file (e.g., jsx-transform.js) with the following code:
*/
/*
Ken Webb
22 Sept 2023
~/nodespace/jsx2xh/chatgptex03/index3.js
- it works!
- I have changed most of the code and added to it. ChatGPT helped me to get started.
TODO
- convert <> ... </> to Xholon <_-.frag> ,,, </_-.frag>
- embed each XML string within a pair of back ticks ex: `<div>...</div>`
*/
const recast = require('recast');
const acorn = require('acorn');
const jsxParser = require('acorn-jsx');
// Your JSX code
const code1 = '<Thumbnail video={video} />'; // OK
const code2 = `function Video({video}) {return (<><div><Thumbnail video={video} /><a href={video.url}><h3>{video.title}</h3><p>{video.description}</p></a><LikeButton video={video} /></div></>);}`; // OK from ksw12.js
const code3 = `function Video({video}) {return (<div><Thumbnail video={video} /><a href={video.url}><h3>{video.title}</h3><p>{video.description}</p></a><LikeButton video={video} /></div>);}`; // OK
// https://react.dev/learn#writing-markup-with-jsx
// React components are JavaScript functions that return markup:
const code4 = `function MyButton() {
return (
<button>I'm a button</button>
);
}`;
// Now that you’ve declared MyButton, you can nest it into another component:
const code5 = `export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}`;
// Notice that <MyButton /> starts with a capital letter. That’s how you know it’s a React component. React component names must always start with a capital letter, while HTML tags must be lowercase.
// Have a look at the result:
const code6 = `function MyButton() {
return (
<button>
I'm a button
</button>
);
}
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
`;
//
const code7 = `function AboutPage() {
return (
<>
<h1>About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}`;
// You can put more complex expressions inside the JSX curly braces too, for example, string concatenation:
// TODO KSW this example has a few difficulties
const code8 = `const user = {
name: 'Hedy Lamarr',
imageUrl: 'https://i.imgur.com/yXOvdOSs.jpg',
imageSize: 90,
};
export default function Profile() {
return (
<>
<h1>{user.name}</h1>
<img
className="avatar"
src={user.imageUrl}
alt={'Photo of ' + user.name}
style={{
width: user.imageSize,
height: user.imageSize
}}
/>
</>
);
}`;
// In React, there is no special syntax for writing conditions. Instead, you’ll use the same techniques as you use when writing regular JavaScript code. For example, you can use an if statement to conditionally include JSX:
const code9 = `let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);`;
// SyntaxError: 'return' outside of function (7:0)
// see: reactjs02.js for a manual Xholonic version
const code9b = `function MyButton() {
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
)};`;
// TypeError: Cannot read property 'name' of undefined
const jsxCode = code3;
console.log(jsxCode + "\n");
// Parse the JSX code with Acorn and Acorn-JSX
const ast = acorn.Parser.extend(jsxParser()).parse(jsxCode, {
sourceType: 'module',
ecmaVersion: 2020,
plugins: {
jsx: true,
},
});
// Transform JSX attributes
const transformedAst = transformJSXAttributes(ast, "");
// Generate the transformed code using Recast
const transformedCode = recast.print(transformedAst).code;
console.log("\n" + transformedCode); // result: <Thumbnail video="\"${video}\"" />;
// this function uses recursive descent
function transformJSXAttributes(node, indent) {
const val = node.value ? node.value.toString() : node.value
console.log(indent, node.type, node.name, val);
if (node.type === 'JSXFragment') {
//console.log(node);
// TODO convert <> ... </> to Xholon <_-.frag> ,,, </_-.frag>
//node.type = 'JSXElement';
//node.name = '_-.frag'; // TODO do this in the correct way
//console.log(node);
}
else if (node.type === 'JSXElement') {
//console.log(node.children);
for (const attr of node.openingElement.attributes) {
if (attr.type === 'JSXAttribute') {
const { name, value } = attr;
//if (name.type === 'JSXIdentifier' && name.name === 'video' && value) { // from chatGPT
if (name.type === 'JSXIdentifier' && value) {
if (value.type === 'JSXExpressionContainer') {
// Transform JSXExpressionContainer into a string with ${}
value.type = 'Literal';
//value.value = `"${'${' + recast.print(value.expression).code + '}"'}`; // from chatGPT NO
value.value = '${' + recast.print(value.expression).code + '}'; // YES
}
}
}
}
const childarr = node.children;
if (childarr) {
//console.log(childarr.length);
for (const child of childarr) {
//console.log(child.type);
if (child.type === 'JSXExpressionContainer') {
child.type = 'Literal';
child.value = '${' + child.expression.object.name + '.' + child.expression.property.name + '}';
}
}
}
}
else if (node.type === 'FunctionDeclaration') {
//console.log(indent + "@@@@@FunctionDeclaration");
}
for (const key in node) {
if (node[key] && typeof node[key] === 'object') {
transformJSXAttributes(node[key], indent + " ");
}
else if (node[key] && typeof node[key] === 'string') {
//console.log(node[key]);
}
}
return node;
}
/**
This code:
Parses the JSX code using Acorn with the Acorn-JSX plugin.
Transforms the JSX attributes as per your requirement (converting {video} to "${video}").
Generates the transformed code using Recast.
Replace the jsxCode variable with your actual JSX code, and when you run this script,
it will print the transformed JSX code to the console.
Make sure to adjust the code as needed for your specific use case,
including loading and saving JSX code from/to files if necessary.
*/
/**
Explore similarities between Xholon and ReactJS.
Ken Webb
13 Sept 2023
reactjs01.js
TODO use babel to transform reactjs to Xholon ?
- Xholon workbook: Transform ReactJS to Xholon
*/
// https://react.dev/
/*
function Video({ video }) {
return (
<div>
<Thumbnail video={video} />
<a href={video.url}>
<h3>{video.title}</h3>
<p>{video.description}</p>
</a>
<LikeButton video={video} />
</div>
);
}
*/
// a Xholonic version
((video) => {
const Video = (video) => {
return (
`<div>
<Thumbnail video="${video}" />
<a href="${video.url}">
<h3>${video.title}</h3>
<p>${video.description}</p>
</a>
<LikeButton video="${video}" />
</div>`
);
}
const xmlstr = Video(video);
xh.root().append(xmlstr);
return xmlstr;
})({
url: "https://www.primordion.com/choco",
title: "Choco Loco",
description: "Choco goes crazy."
})
/* result
'<div>
<Thumbnail video="[object Object]" />
<a href="https://www.primordion.com/choco">
<h3>Choco Loco</h3>
<p>Choco goes crazy.</p>
</a>
<LikeButton video="[object Object]" />
</div>'
*/
// this also works, 22 Sept 2023
// a Xholonic version
((video) => {
const Video = (video) => `<div><Thumbnail video="${video}" /><a href="${video.url}"><h3>${video.title}</h3><p>${video.description}</p></a><LikeButton video="${video}" /></div>`
const xmlstr = Video(video);
xh.root().append(xmlstr);
return xmlstr;
})({
url: "https://www.primordion.com/choco",
title: "Choco Loco",
description: "Choco goes crazy."
})
/**
Explore similarities between Xholon and ReactJS.
Ken Webb
22 Sept 2023
reactjs02.js
*/
// https://react.dev/learn#writing-markup-with-jsx
/*
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
*/
// a Xholonic version
((isLoggedIn) => {
function MyButton() {
let content;
if (isLoggedIn) {
content = "<AdminPanel />";
} else {
content = "<LoginForm />";
}
return (
`<div>
${content}
</div>`
);
}
const xmlstr = MyButton();
return xmlstr;
})(true)
/* result
"<div>
<AdminPanel />
</div>"
*/
<?xml version="1.0" encoding="UTF-8"?>
<!--Xholon Workbook http://www.primordion.com/Xholon/gwt/ MIT License, Copyright (C) Ken Webb, Wed Sep 27 2023 11:53:30 GMT-0400 (Eastern Daylight Saving Time)-->
<XholonWorkbook>
<Notes><![CDATA[
Xholon
------
Title: Transform ReactJS to Xholon
Description:
Url: http://www.primordion.com/Xholon/gwt/
InternalName: 62ee0cfeb3584c95dae72d4960fcb7e0
Keywords:
My Notes
--------
13 Sept 2023
In this workbook I explore how to transform ReactJS JSX to Xholon format.
See my examples in: ~/nodespace/jsx2xh
chatgptex01
chatgptex02
recast01
### Export result as XML
- I used Export > Xml on board node
Xml
board
{"xhAttrStyle":1,"nameTemplate":"^^C^^^","xhAttrReturnAll":false,"writeStartDocument":false,"writeXholonId":false,"writeXholonRoleName":false,"writePorts":false,"writeAnnotations":false,"shouldPrettyPrint":true,"writeAttributes":false,"writeStandardAttributes":false,"shouldWriteVal":false,"shouldWriteAllPorts":false,"shouldWriteLinks":false,"showComments":false}
If I paste the resulting XML into the Xholon GUI HTML element <div id="xhappspecific"></div>
- it shows the 3 x 3 grid
### Event Handler
button is written to console as:
<button className="square" onClick="console.log('clicked ' + this.textContent)">
1
</button>
If I paste this into the HTML in the browser, and click on that button, then "clicked N" is written to the Dev Tools console.
### Edit HTML
- paste or edit the following to appspecific element
<Board>
<div>Winner: Human</div>
<div>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">1</button>
</Square>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">2</button>
</Square>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">3</button>
</Square>
</div>
<div>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">4</button>
</Square>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">5</button>
</Square>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">6</button>
</Square>
</div>
<div>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">7</button>
</Square>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">8</button>
</Square>
<Square>
<button className="square" onClick="console.log('clicked ' + this.textContent)">9</button>
</Square>
</div>
</Board>
### TODO
27 Sept 2023
- the existing HtmlExporter is designed for Board
- write a new HtmlExporter to handle Game
- work on Game and Board to get the final app working
- at this point, everything compiles and basically runs
### References
(1) https://react.dev/
I am using the initial simple example as a first test.
see: ~/gwtspace/Xholon/Xholon/script/javascript/reactjs01.js
(2) https://babeljs.io/
I will follow the general instructions here.
(3) ChatGPT
see: ~/A001_OpenAI/chatGPT48_babel.txt
(4) https://babeljs.io/videos/
- maybe I should see some of these? But they are oldish.
(5) https://astexplorer.net/
- has a good default example that reverses the names of Identifiers, using the followng visitor
export default function (babel) {
const { types: t } = babel;
return {
name: "ast-transform", // not required
visitor: {
Identifier(path) {
path.node.name = path.node.name.split('').reverse().join('');
}
}
};
}
(5b) https://github.com/fkling/astexplorer/blob/master/README.md
AST explorer
Paste or drop code into the editor and inspect the generated AST on https://astexplorer.net/
The AST explorer provides following code parsers:
...
(6) https://www.youtube.com/watch?v=VBscbcm2Mok&t=1253s
Writing custom Babel and ESLint plugins with ASTs (Open West 2017), Kent C. Dodds
(7) https://github.com/babel/babel
The compiler for writing next generation JavaScript.
Babel (pronounced "babble") is a community-driven project used by many companies and projects,
and is maintained by a group of volunteers.
(8) https://babel.dev/docs/babel-plugin-transform-react-jsx
This plugin is included in @babel/preset-react
- the page includes a bunch of examples
(9) https://github.com/jamiebuilds/babel-handbook/tree/master
Babel Handbook, Written by Jamie Kyle
A guided handbook on how to use Babel and how to create plugins for Babel.
) https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/user-handbook.md
This document covers everything you ever wanted to know about using Babel and related tooling.
(10) https://babeljs.io/docs/babel-traverse
We can use it alongside the babel parser to traverse and update nodes:
(11) https://dev.to/pulkitnagpal/transpile-jsx-using-your-own-custom-built-babel-plugin-4888
pulkitnagpal, Posted on Aug 16, 2020
Transpile JSX using your own custom built babel plugin
Ever wondered how does react jsx code (<div>Hello World</div>) gets compiled to React.createElement("div", null, "Hello World").
This blog is all about this compilation process by taking help from the source code of babel-preset-react and trying to build our own custom plugin.
(12) https://www.digitalocean.com/community/tutorials/js-traversing-ast
// Tutorial //, Read JavaScript Source Code, Using an AST, Published on February 8, 2019, Bart Ledoux
The following example is small.
Our mission, should you choose to accept it, will be to extract the names of all the functions exposed in the global scope.
- has a list of parsers
- the example is fairly detailed
(13) https://github.com/acornjs/acorn-jsx
) https://github.com/acornjs/acorn-jsx/blob/main/test/tests-jsx.js
(14) https://legacy.reactjs.org/docs/jsx-in-depth.html
Fundamentally, JSX just provides syntactic sugar for the React.createElement(component, props, ...children) function. The JSX code:
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
compiles into:
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
) https://react.dev/learn#writing-markup-with-jsx
(15) https://transform.tools/html-to-jsx
- convert from HTML to JSX
- this site has many similar transformers
(16) https://github.com/acornjs/acorn
A tiny, fast JavaScript parser, written completely in JavaScript.
This repository holds three packages:
acorn: The main parser
acorn-loose: The error-tolerant parser
acorn-walk: The syntax tree walker
(17a) https://github.com/acornjs/acorn/tree/master/acorn/
(17b) https://github.com/acornjs/acorn/tree/master/acorn-loose/
(17c) https://github.com/acornjs/acorn/tree/master/acorn-walk/
(18) https://github.com/estree/estree
) https://github.com/estree/estree/blob/master/es5.md
The ESTree Spec
Once upon a time, an unsuspecting Mozilla engineer created an API in Firefox that exposed the SpiderMonkey engine's JavaScript parser as a JavaScript API.
Said engineer documented the format it produced, and this format caught on as a lingua franca for tools that manipulate JavaScript source code.
Meanwhile JavaScript is evolving.
This site will serve as a community standard for people involved in building and using these tools to help evolve this format
to keep up with the evolution of the JavaScript language.
(19) https://github.com/estree/formal
ESTree machine-readable data (+generator)
(20) https://github.com/benjamn/recast
JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator
Recast exposes two essential interfaces, one for parsing JavaScript code (require("recast").parse)
and the other for reprinting modified syntax trees (require("recast").print).
- used in ref [12]
(21) https://esprima.org/
Esprima is a high performance, standard-compliant ECMAScript parser written in ECMAScript (also popularly known as JavaScript).
Features
- Full support for ECMAScript 2019 (ECMA-262 10th Edition)
- Sensible syntax tree format, with optional node location info
- Experimental support for JSX, a syntax extension for React
- Heavily tested (~1600 tests with full code coverage)
) https://www.npmjs.com/package/esprima
(22) https://www.npmjs.com/search?q=keywords:ast
- lots of interesting projects
(23) https://react.dev/learn/tutorial-tic-tac-toe
Create a Xholonic version of this React tutorial.
- use DefaultContent
- perhaps use the GUI I created for my MeTTTa Xholon app.
(24) ~/gwtspace/Xholon/Xholon/script/javascript/reactjsUseState01.js
- how to implement react useState in vanilla javascript
(24b) https://stackoverflow.com/questions/64744252/how-to-replicate-usestate-with-vanilla-js
- I will implement some of what I found here
- note that "count" in the example is a function rather than a constant value
(24c) https://codesandbox.io/s/usestate-implementation-in-vanilla-js-ff4rx
- good example that runs in browser
]]></Notes>
<_-.XholonClass>
<PhysicalSystem/>
<Square superClass="Script"/>
<Board superClass="Script"/>
<Game superClass="Script"/>
<TestUseState superClass="Script"/>
<HtmlExporter superClass="Script"/>
</_-.XholonClass>
<xholonClassDetails>
<!-- from ref[23]:
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
-->
<Square><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode;
//$wnd.console.log(me.name());
function Square({ value, onSquareClick }) {
return (
`<button className="square" onClick="${onSquareClick}">
${value}
</button>`
);
}
var sq = Square({value: me.value, onSquareClick: "console.log('clicked ' + this.textContent); this.textContent = 'X';"});
//$wnd.console.log(sq);
me.append(sq);
}
}
//# sourceURL=SquareTest.js
]]></DefaultContent></Square>
<!-- from ref[23]:
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
onPlay(nextSquares);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
-->
<Board><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode;
function calculateWinner(squares) {
return "Human";
}
function Board({ xIsNext, squares, onPlay }) {
function handleClick(i) {
if (calculateWinner(squares) || squares[i]) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = 'X';
} else {
nextSquares[i] = 'O';
}
onPlay(nextSquares);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
return (
`<_-.board>
<div className="status">${status}</div>
<div className="board-row">
<Square value="${squares[0]}" onSquareClick="${() => handleClick(0)}" />
<Square value="${squares[1]}" onSquareClick="${() => handleClick(1)}" />
<Square value="${squares[2]}" onSquareClick="${() => handleClick(2)}" />
</div>
<div className="board-row">
<Square value="${squares[3]}" onSquareClick="${() => handleClick(3)}" />
<Square value="${squares[4]}" onSquareClick="${() => handleClick(4)}" />
<Square value="${squares[5]}" onSquareClick="${() => handleClick(5)}" />
</div>
<div className="board-row">
<Square value="${squares[6]}" onSquareClick="${() => handleClick(6)}" />
<Square value="${squares[7]}" onSquareClick="${() => handleClick(7)}" />
<Square value="${squares[8]}" onSquareClick="${() => handleClick(8)}" />
</div>
</_-.board>`
);
}
var brd = Board({xIsNext: true, squares: [1,2,3,4,5,6,7,8,9], onPlay: "play"});
//$wnd.console.log(brd);
me.append(brd);
// the following writes the resulting XML to a Xholon GUI tab:
//$wnd.xh.xport("Xml", me, '{"xhAttrStyle":1,"nameTemplate":"^^C^^^","xhAttrReturnAll":false,"writeStartDocument":false,"writeXholonId":false,"writeXholonRoleName":false,"writePorts":false,"writeAnnotations":false,"shouldPrettyPrint":true,"writeAttributes":false,"writeStandardAttributes":false,"shouldWriteVal":false,"shouldWriteAllPorts":false,"shouldWriteLinks":false,"showComments":false}');
}
}
//# sourceURL=BoardTest.js
]]></DefaultContent></Board>
<!-- from ref[23]:
export default function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const xIsNext = currentMove % 2 === 0;
const currentSquares = history[currentMove];
function handlePlay(nextSquares) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
const moves = history.map((squares, move) => {
let description;
if (move > 0) {
description = 'Go to move #' + move;
} else {
description = 'Go to game start';
}
return (
<li key={move}>
<button onClick={() => jumpTo(move)}>{description}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
</div>
<div className="game-info">
<ol>{moves}</ol>
</div>
</div>
);
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
-->
<Game><DefaultContent><![CDATA[
var me, beh = {
postConfigure: function() {
me = this.cnode;
$wnd.console.log(me.name());
const useState = (defaultValue) => {
let value = defaultValue;
const getValue = () => value
const setValue = newValue => value = newValue
return [getValue, setValue]
}
function Game() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const xIsNext = currentMove() % 2 === 0;
const currentSquares = history()[currentMove()];
console.log(history());
console.log(currentMove());
console.log(xIsNext);
console.log(currentSquares);
function handlePlay(nextSquares) {
const nextHistory = [...history().slice(0, currentMove() + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
function jumpTo(nextMove) {
setCurrentMove(nextMove);
}
const moves = history().map((squares, move) => {
let description;
if (move > 0) {
description = 'Go to move #' + move;
} else {
description = 'Go to game start';
}
return (
`<li key="${move}">
<button onClick="{() => jumpTo(move)}">${description}</button>
</li>`
);
});
console.log(moves);
return (
`<div className="game">
<div className="game-board">
<Board xIsNext="${xIsNext}" squares="${currentSquares}" onPlay="${handlePlay}" />
</div>
<div className="game-info">
<ol>${moves}</ol>
</div>
</div>`
);
} // end function Game()
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
var game = Game();
console.log(game);
me.append(game);
} // end postConfigure
}
//# sourceURL=GameTest.js
]]></DefaultContent></Game>
<TestUseState><DefaultContent><![CDATA[
((initValues) => {
const useState = (defaultValue) => {
let value = defaultValue;
const getValue = () => value
const setValue = newValue => value = newValue
return [getValue, setValue]
}
const test1 = (initValue) => {
const [count, setCount] = useState(initValue);
console.log(count());
setCount(count() + 1);
console.log(count());
setCount(count() + 1);
console.log(count());
setCount(count() + 1);
console.log(count());
}
const test2 = (initValue) => {
const [history, setHistory] = useState(initValue);
const [currentMove, setCurrentMove] = useState(0);
console.log(history()[currentMove()]);
console.log(history()[currentMove()][0]);
}
test1(initValues[0]);
test1(initValues[1]);
test2(initValues[2]);
})([13, "21", [Array(9).fill(null)]])
// works for integers (addition)
// and works for strings (concatenation)
// and works for history array
//# sourceURL=TestUseState.js
]]></DefaultContent></TestUseState>
<HtmlExporter><DefaultContent><![CDATA[
(() => {
const board = this.xpath("../Board");
// write Board subtree as an HTML string
const asHtmlStr = (node, indent) => {
let str = "";
const nodename = node.xhc().name();
if (node.first()) {
str += `${indent}<${nodename}>\n`;
str += asHtmlStr(node.first(), indent + " ");
str += `${indent}</${nodename}>\n`;
}
else {
if (nodename === "button") {
str += `${indent}<${nodename} className="square" onClick="${node.onClick};">${node.text()}</${nodename}>\n`;
}
else {
str += `${indent}<${nodename}/>\n`;
}
}
if (node.next()) {
str += asHtmlStr(node.next(), indent);
}
return str;
}
const str = asHtmlStr(board, "");
// insert in the HTML page
const xhappspecific = $doc.querySelector("#xhappspecific");
xhappspecific.innerHTML = str;
})()
//# sourceURL=HtmlExporter.js
]]></DefaultContent></HtmlExporter>
</xholonClassDetails>
<PhysicalSystem>
<!-- see Xml2Xholon - DefaultContent only works if I include RoomModel somewhere before I need to use DefaultContent -->
<RoomModel/>
<!-- put this node first, because a sequence of Script nodes is configured in reverse order -->
<HtmlExporter/>
<!-- simple test of a single Square -->
<!--<Square/>-->
<Board/>
<!--<TestUseState/>-->
<!--<Game/> this works, but HTML does not yet display -->
</PhysicalSystem>
<SvgClient><Attribute_String roleName="svgUri"><![CDATA[data:image/svg+xml,
<svg width="100" height="50" xmlns="http://www.w3.org/2000/svg">
<g>
<title>Board</title>
<rect id="PhysicalSystem/Board" fill="#98FB98" height="50" width="50" x="25" y="0"/>
<g>
<title>Square</title>
<rect id="PhysicalSystem/Square" fill="#6AB06A" height="50" width="10" x="80" y="0"/>
</g>
</g>
</svg>
]]></Attribute_String><Attribute_String roleName="setup">${MODELNAME_DEFAULT},${SVGURI_DEFAULT}</Attribute_String></SvgClient>
</XholonWorkbook>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment