Twine is a neat ‘open-source tool for telling interactive, nonlinear stories’, as its blurb says. It lets you write stories using variables, conditionals, and other simple programming primitives. It supports three different languages, but I’ll focus only on the default, Harlowe. Some example code:
Hello $name. (if: $isMale)[You're male!](else:)[You're female!].
This works pretty well, but it can be really cumbersome to do anything complicated. I was curious to see how well it could be integrated into React. Harlowe purposefully does not expose any of its internals, so our solution is going to need to be pretty hacky.
The solution that I came up with was to add some javascript to a startup tag that Harlowe will run, and attach the necessary private variable to the global window variable. Then to load and run the Harlowe engine in the react componentWillMount function. Like so:
import React, { Component } from 'react'; import './App.css'; class App extends Component { componentWillMount() { const script = document.createElement("script"); script.src = "harlowe-min.js"; script.async = true; document.body.appendChild(script); } render() { return ( <div className="App"> <header id="header">My header </header> <div id="container"> <main id="center" className="column"> <article> <tw-story dangerouslySetInnerHTML={{__html: ""}}></tw-story> <tw-storydata startnode={1} style={{display:"none"}}> <script role="script" id="twine-user-script" type="text/twine-javascript">{` if (!window.harlowe){ window.harlowe = State; } `}</script> <tw-passagedata pid={1} name="Start">**Success!**(set: $foo to 1) Foo is $foo</tw-passagedata> </tw-storydata> </article> </main> <nav id="left" className="column"> <h3>Left heading</h3> <ul> <li><a href="#">Some Link</a></li> </ul> </nav> <div id="right" className="column"> <h3>Right heading</h3> </div> </div> <div id="footer-wrapper"> <footer id="footer"> </footer></div> </div> ); } } export default App;
It seemed to work just fine without
dangerouslySetInnerHTML={{__html: ""}}
But I added it to make it clear to react that we don’t want react to manage this.
Unfortunately I couldn’t proceed further than this with trying to make them nicely integrate. I couldn’t see a way to hook into the engine to know when a variable is updated via, say, a user clicking on a (link:..)[].
There is a VarRef object that would allow exactly this with jQuery, by doing something like:
var setState = this.setState.bind(this); VarRef.on('set', (obj, name, value) => { if (obj === State.variables) { setState({[name]: value}) } })
Unfortunately the VarRef is not exposed in any scope that I can find. The source code is available, so the VarRef can be exposed with a fairly simple change, but not without doing that, as far I can tell.