From 7348279e8bfdc0a0c3e822df2d129c6e143b0d63 Mon Sep 17 00:00:00 2001 From: Andrew Schreiber Date: Sun, 29 Apr 2018 00:38:16 -0400 Subject: [PATCH 1/5] react ui --- FRC Assistant.ink | 7 +- docs/FRC Assistant.js | 1 - docs/ink.js | 6697 ------------- docs/main.js | 4 + frontend/.gitignore | 21 + frontend/README.md | 2444 +++++ frontend/package-lock.json | 11298 ++++++++++++++++++++++ frontend/package.json | 17 + frontend/public/favicon.ico | Bin 0 -> 3870 bytes frontend/public/index.html | 40 + frontend/public/manifest.json | 15 + frontend/src/App.css | 68 + frontend/src/App.js | 45 + frontend/src/App.test.js | 9 + frontend/src/index.css | 5 + frontend/src/index.js | 8 + frontend/src/logo.svg | 7 + frontend/src/registerServiceWorker.js | 117 + frontend/src/storyData/FRC Assistant.js | 1 + 19 files changed, 14105 insertions(+), 6699 deletions(-) delete mode 100644 docs/FRC Assistant.js delete mode 100644 docs/ink.js create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/favicon.ico create mode 100644 frontend/public/index.html create mode 100644 frontend/public/manifest.json create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.js create mode 100644 frontend/src/App.test.js create mode 100644 frontend/src/index.css create mode 100644 frontend/src/index.js create mode 100644 frontend/src/logo.svg create mode 100644 frontend/src/registerServiceWorker.js create mode 100644 frontend/src/storyData/FRC Assistant.js diff --git a/FRC Assistant.ink b/FRC Assistant.ink index bbbcdea..12f108a 100644 --- a/FRC Assistant.ink +++ b/FRC Assistant.ink @@ -1,6 +1,7 @@ INCLUDE Drivetrain.ink Hey, I'm here to help give you some advice on how to build your robot this year. I'm just a compendium of best practices and resources, at the end of the day it is your robot, you should build something that meets your team's goals, whatever they may be. + # { "type":"image" } So let's get started... -> main_start @@ -15,7 +16,11 @@ What {~what can I help you with?| are you struggling with?} == not_implemented -Hey, sorry, I don't know how to help you with this yet. If you want to help build this check out my source on github<\/a>. +Hey, sorry, I don't know how to help you with this yet. If you want to help build this check out my source on + # {"type": "resource", "data":"http:\/\/github.com\/schreiaj\/frc-assistant", "thumbnail": "https:\/\/avatars3.githubusercontent.com\/u\/329127?s=460&v=4"} + + -> main_start + -> END \ No newline at end of file diff --git a/docs/FRC Assistant.js b/docs/FRC Assistant.js deleted file mode 100644 index b296656..0000000 --- a/docs/FRC Assistant.js +++ /dev/null @@ -1 +0,0 @@ -var storyContent = {"inkVersion":17,"root":["\n","^Hey, I'm here to help give you some advice on how to build your robot this year. I'm just a compendium of best practices and resources, at the end of the day it is your robot, you should build something that meets your team's goals, whatever they may be.","\n","^So let's get started...","\n",{"->":"main_start"},"done",{"main_start":["^What ",["G>",["ev","visit",2,"seq","/ev","ev","du",0,"==","/ev",{"->":".^.s0","c":true},"ev","du",1,"==","/ev",{"->":".^.s1","c":true},"nop",{"s0":["pop","^what can I help you with?",{"->":".^.^.17"},null],"s1":["pop","^ are you struggling with?",{"->":".^.^.17"},null],"#f":5}],"G<",null],"\n",["ev",{"^->":"main_start.3.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^I need to figure out my drivetrain",{"->":"$r","var":true},null],"c":["ev",{"^->":"main_start.3.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"drivetrain"},{"#f":7}]}],["ev",{"^->":"main_start.4.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^How do I grab the game piece?",{"->":"$r","var":true},null],"c":["ev",{"^->":"main_start.4.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"not_implemented"},{"#f":7}]}],["ev",{"^->":"main_start.5.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^I have the game piece but I don't know what to do next...",{"->":"$r","var":true},null],"c":["ev",{"^->":"main_start.5.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"not_implemented"},{"#f":7}]}],{"#f":3}],"not_implemented":["^Hey, sorry, I don't know how to help you with this yet. If you want to help build this check out my source on github.","\n",{"->":"main_start"},"end",{"#f":3}],"drivetrain":["^Ok, great. So to start, let's talk about the kit of parts drivetrain. Are you planning on using that?","\n",["ev",{"^->":"drivetrain.2.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^Yes, it's already built but I'm having problems",{"->":"$r","var":true},null],"c":["ev",{"^->":"drivetrain.2.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"not_implemented"},{"#f":7}]}],["ev",{"^->":"drivetrain.3.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^We want to but we're not sure how to start...",{"->":"$r","var":true},null],"c":["ev",{"^->":"drivetrain.3.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"not_implemented"},{"#f":7}]}],["ev",{"^->":"drivetrain.4.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^We're not sure why we should",{"->":"$r","var":true},null],"c":["ev",{"^->":"drivetrain.4.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"not_implemented"},{"#f":7}]}],["ev",{"^->":"drivetrain.5.$r1"},{"temp=":"$r"},"str",{"->":".^.s"},[{"#n":"$r1"}],"/str","/ev",{"*":".^.c","flg":2},{"s":["^No, we're definitetly building something custom...",{"->":"$r","var":true},null],"c":["ev",{"^->":"drivetrain.5.c.$r2"},"/ev",{"temp=":"$r"},{"->":".^.^.s"},[{"#n":"$r2"}],"\n","\n",{"->":"not_implemented"},{"#f":7}]}],{"#f":3}],"#f":3}],"listDefs":{}}; \ No newline at end of file diff --git a/docs/ink.js b/docs/ink.js deleted file mode 100644 index 2c81141..0000000 --- a/docs/ink.js +++ /dev/null @@ -1,6697 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('inkjs', ['exports'], factory) : - (factory((global.inkjs = global.inkjs || {}))); -}(this, (function (exports) { 'use strict'; - - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - - - - - - - - - - - - var classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - }; - - var createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - - - - - - - - var _extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; - }; - - - - var inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - }; - - - - - - - - - - - - var possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return call && (typeof call === "object" || typeof call === "function") ? call : self; - }; - - var Path$1 = function () { - function Path() /*polymorphic constructor*/{ - classCallCheck(this, Path); - - this._isRelative; - this._components = []; - - if (typeof arguments[0] == 'string') { - this.componentsString = arguments[0]; - } else if (arguments[0] instanceof Component && arguments[1] instanceof Path) { - this._components.push(arguments[0]); - this._components = this._components.concat(arguments[1]); - } else if (arguments[0] instanceof Array) { - this._components = this._components.concat(arguments[0]); - this._isRelative = !!arguments[1]; - } - } - - createClass(Path, [{ - key: "PathByAppendingPath", - value: function PathByAppendingPath(pathToAppend) { - var p = new Path(); - - var upwardMoves = 0; - for (var i = 0; i < pathToAppend.components.length; ++i) { - if (pathToAppend.components[i].isParent) { - upwardMoves++; - } else { - break; - } - } - - for (var i = 0; i < this.components.length - upwardMoves; ++i) { - p.components.push(this.components[i]); - } - - for (var i = upwardMoves; i < pathToAppend.components.length; ++i) { - p.components.push(pathToAppend.components[i]); - } - - return p; - } - }, { - key: "toString", - value: function toString() { - return this.componentsString; - } - }, { - key: "Equals", - value: function Equals(otherPath) { - if (otherPath == null) return false; - - if (otherPath.components.length != this.components.length) return false; - - if (otherPath.isRelative != this.isRelative) return false; - - //the original code uses SequenceEqual here, so we need to iterate over the components manually. - for (var i = 0, l = otherPath.components.length; i < l; i++) { - //it's not quite clear whether this test should use Equals or a simple == operator, see https://github.com/y-lohse/inkjs/issues/22 - if (!otherPath.components[i].Equals(this.components[i])) return false; - } - - return true; - } - }, { - key: "isRelative", - get: function get$$1() { - return this._isRelative; - } - }, { - key: "components", - get: function get$$1() { - return this._components; - } - }, { - key: "head", - get: function get$$1() { - if (this.components.length > 0) { - return this.components[0]; - } else { - return null; - } - } - }, { - key: "tail", - get: function get$$1() { - if (this.components.length >= 2) { - var tailComps = this.components.slice(1, this.components.length); //careful, the original code uses length-1 here. This is because the second argument of List.GetRange is a number of elements to extract, wherease Array.slice uses an index - return new Path(tailComps); - } else { - return Path.self; - } - } - }, { - key: "length", - get: function get$$1() { - return this.components.length; - } - }, { - key: "lastComponent", - get: function get$$1() { - if (this.components.length > 0) { - return this.components[this.components.length - 1]; - } else { - return null; - } - } - }, { - key: "containsNamedComponent", - get: function get$$1() { - for (var i = 0, l = this.components.length; i < l; i++) { - if (!this.components[i].isIndex) { - return true; - } - } - return false; - } - }, { - key: "componentsString", - get: function get$$1() { - var compsStr = this.components.join("."); - if (this.isRelative) return "." + compsStr;else return compsStr; - }, - set: function set$$1(value) { - var _this = this; - - this.components.length = 0; - - var componentsStr = value; - - if (componentsStr == null || componentsStr == '') return; - - // When components start with ".", it indicates a relative path, e.g. - // .^.^.hello.5 - // is equivalent to file system style path: - // ../../hello/5 - if (componentsStr[0] == '.') { - this._isRelative = true; - componentsStr = componentsStr.substring(1); - } - - var componentStrings = componentsStr.split('.'); - componentStrings.forEach(function (str) { - //we need to distinguish between named components that start with a number, eg "42somewhere", and indexed components - //the normal parseInt won't do for the detection because it's too relaxed. - //see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt - if (/^(\-|\+)?([0-9]+|Infinity)$/.test(str)) { - _this.components.push(new Component(parseInt(str))); - } else { - _this.components.push(new Component(str)); - } - }); - } - }], [{ - key: "self", - get: function get$$1() { - var path = new Path(); - path._isRelative = true; - return path; - } - }]); - return Path; - }(); - - var Component = function () { - function Component(indexOrName) { - classCallCheck(this, Component); - - if (typeof indexOrName == 'string') { - this._index = -1; - this._name = indexOrName; - } else { - this._index = parseInt(indexOrName); - this._name = null; - } - } - - createClass(Component, [{ - key: "toString", - value: function toString() { - if (this.isIndex) { - return this.index.toString(); - } else { - return this.name; - } - } - }, { - key: "Equals", - value: function Equals(otherComp) { - if (otherComp != null && otherComp.isIndex == this.isIndex) { - if (this.isIndex) { - return this.index == otherComp.index; - } else { - return this.name == otherComp.name; - } - } - - return false; - } - }, { - key: "index", - get: function get$$1() { - return this._index; - } - }, { - key: "name", - get: function get$$1() { - return this._name; - } - }, { - key: "isIndex", - get: function get$$1() { - return this.index >= 0; - } - }, { - key: "isParent", - get: function get$$1() { - return this.name == Path$1.parentId; - } - }], [{ - key: "ToParent", - value: function ToParent() { - return new Component(Path$1.parentId); - } - }]); - return Component; - }(); - - Path$1.parentId = "^"; - Path$1.Component = Component; - - var Object$1 = function () { - function Object() { - classCallCheck(this, Object); - - this.parent = null; - this._path = null; - } - - createClass(Object, [{ - key: 'ResolvePath', - value: function ResolvePath(path) { - if (path.isRelative) { - var nearestContainer = this; - - if (nearestContainer instanceof Container === false) { - if (this.parent == null) console.warn("Can't resolve relative path because we don't have a parent"); - - nearestContainer = this.parent; - if (nearestContainer.constructor.name !== 'Container') console.warn("Expected parent to be a container"); - - //Debug.Assert (path.components [0].isParent); - path = path.tail; - } - - return nearestContainer.ContentAtPath(path); - } else { - return this.rootContentContainer.ContentAtPath(path); - } - } - }, { - key: 'ConvertPathToRelative', - value: function ConvertPathToRelative(globalPath) { - var ownPath = this.path; - - var minPathLength = Math.min(globalPath.components.length, ownPath.components.length); - var lastSharedPathCompIndex = -1; - - for (var i = 0; i < minPathLength; ++i) { - var ownComp = ownPath.components[i]; - var otherComp = globalPath.components[i]; - - if (ownComp.Equals(otherComp)) { - lastSharedPathCompIndex = i; - } else { - break; - } - } - - // No shared path components, so just use global path - if (lastSharedPathCompIndex == -1) return globalPath; - - var numUpwardsMoves = ownPath.components.length - 1 - lastSharedPathCompIndex; - - var newPathComps = []; - - for (var up = 0; up < numUpwardsMoves; ++up) { - newPathComps.push(Path$1.Component.ToParent()); - }for (var down = lastSharedPathCompIndex + 1; down < globalPath.components.length; ++down) { - newPathComps.push(globalPath.components[down]); - }var relativePath = new Path$1(newPathComps, true); - return relativePath; - } - }, { - key: 'CompactPathString', - value: function CompactPathString(otherPath) { - var globalPathStr = null; - var relativePathStr = null; - - if (otherPath.isRelative) { - relativePathStr = otherPath.componentsString; - globalPathStr = this.path.PathByAppendingPath(otherPath).componentsString; - } else { - var relativePath = this.ConvertPathToRelative(otherPath); - relativePathStr = relativePath.componentsString; - globalPathStr = otherPath.componentsString; - } - - if (relativePathStr.Length < globalPathStr.Length) return relativePathStr;else return globalPathStr; - } - }, { - key: 'Copy', - value: function Copy() { - throw "Not Implemented"; - } - //SetCHild works slightly diferently in the js implementation. SInce we can't pass an objets property by reference, we instead pass the object and the property string. - - }, { - key: 'SetChild', - value: function SetChild(obj, prop, value) { - if (obj[prop]) obj[prop] = null; - - obj[prop] = value; - - if (obj[prop]) obj[prop].parent = this; - } - }, { - key: 'path', - get: function get$$1() { - if (this._path == null) { - - if (this.parent == null) { - this._path = new Path$1(); - } else { - // Maintain a Stack so that the order of the components - // is reversed when they're added to the Path. - // We're iterating up the hierarchy from the leaves/children to the root. - var comps = []; - - var child = this; - // Container container = child.parent as Container; - var container = child.parent; - - while (container instanceof Container) { - - var namedChild = child; - if (namedChild.name && namedChild.hasValidName) { - comps.unshift(new Path$1.Component(namedChild.name)); - } else { - comps.unshift(new Path$1.Component(container.content.indexOf(child))); - } - - child = container; - // container = container.parent as Container; - container = container.parent; - } - - this._path = new Path$1(comps); - } - } - - return this._path; - } - }, { - key: 'rootContentContainer', - get: function get$$1() { - var ancestor = this; - while (ancestor.parent) { - ancestor = ancestor.parent; - } - return ancestor; - } - }]); - return Object; - }(); - - var StringBuilder = function () { - function StringBuilder(str) { - classCallCheck(this, StringBuilder); - - str = typeof str !== 'undefined' ? str.toString() : ''; - this._string = str; - } - - createClass(StringBuilder, [{ - key: 'Append', - value: function Append(str) { - this._string += str; - } - }, { - key: 'AppendLine', - value: function AppendLine(str) { - if (typeof str !== 'undefined') this.Append(str); - this._string += "\n"; - } - }, { - key: 'AppendFormat', - value: function AppendFormat(format) { - //taken from http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format - var args = Array.prototype.slice.call(arguments, 1); - return format.replace(/{(\d+)}/g, function (match, number) { - return typeof args[number] != 'undefined' ? args[number] : match; - }); - } - }, { - key: 'toString', - value: function toString() { - return this._string; - } - }, { - key: 'Length', - get: function get$$1() { - return this._string.length; - } - }]); - return StringBuilder; - }(); - - var InkListItem = function () { - function InkListItem(fullNameOrOriginName, itemName) { - classCallCheck(this, InkListItem); - - if (itemName !== undefined) { - this.originName = fullNameOrOriginName; - this.itemName = itemName; - } else { - var nameParts = fullNameOrOriginName.toString().split('.'); - this.originName = nameParts[0]; - this.itemName = nameParts[1]; - } - } - - createClass(InkListItem, [{ - key: 'isNull', - value: function isNull() { - return this.originName == null && this.itemName == null; - } - }, { - key: 'toString', - value: function toString() { - return this.fullname; - } - }, { - key: 'Equals', - value: function Equals(obj) { - if (obj instanceof InkListItem) { - // var otherItem = (InkListItem)obj; - var otherItem = obj; - return otherItem.itemName == this.itemName && otherItem.originName == this.originName; - } - - return false; - } - //GetHashCode not implemented - - }, { - key: 'toString', - value: function toString() { - //WARNING: experimental. InkListItem are structs and are used as keys inside hashes. In js, we can't use an object as a key, as the key needs to be a string. C# gets around that with the internal GetHashCode, and the js equivalent to that is toString. So here, toString acts as C#'s GetHashCode - var originCode = '0'; - var itemCode = this.itemName ? this.itemName.toString() : 'null'; - if (this.originName != null) originCode = this.originName.toString(); - - return originCode + itemCode; - } - }, { - key: 'fullName', - get: function get$$1() { - return (this.originName !== null ? this.originName : "?") + "." + this.itemName; - } - }], [{ - key: 'Null', - value: function Null() { - return new InkListItem(null, null); - } - }]); - return InkListItem; - }(); - - //in C#, rawlists are based on dictionnary; the equivalent of a dictionnary in js is Object, but we can't use that or it will conflate dictionnary items and InkList class properties. - //instead InkList-js has a special _values property wich contains the actual "Dictionnary", and a few Dictionnary methods are re-implemented on InkList. This also means directly iterating over the InkList won't work as expected. Maybe we can return a proxy if that's required. - //@TODO: actually we could use a Map for this. - var InkList = function () { - function InkList(polymorphicArgument, originStory) { - var _this = this; - - classCallCheck(this, InkList); - - this._keys = {}; - this._values = {}; - this.origins = null; - this._originNames = null; - - //polymorphioc constructor - if (polymorphicArgument) { - if (polymorphicArgument instanceof InkList) { - var otherList = polymorphicArgument; - otherList.forEach(function (kv) { - _this.Add(kv.Key, kv.Value); - }); - - this._originNames = otherList._originNames; - } else if (typeof polymorphicArgument === 'string') { - this.SetInitialOriginName(polymorphicArgument); - - var def = null; - if (def = originStory.listDefinitions.TryGetDefinition(polymorphicArgument, def)) { - this.origins = [def]; - } else { - throw new Error("InkList origin could not be found in story when constructing new list: " + singleOriginListName); - } - } else if (polymorphicArgument.hasOwnProperty('Key') && polymorphicArgument.hasOwnProperty('Value')) { - var singleElement = polymorphicArgument; - this.Add(singleElement.Key, singleElement.Value); - } - } - } - - createClass(InkList, [{ - key: 'forEach', - value: function forEach(fn) { - for (var key in this._values) { - fn({ - Key: this._keys[key], - Value: this._values[key] - }); - } - } - }, { - key: 'AddItem', - value: function AddItem(itemOrItemName) { - var _this2 = this; - - if (itemOrItemName instanceof InkListItem) { - var item = itemOrItemName; - - if (item.originName == null) { - this.AddItem(item.itemName); - return; - } - - this.origins.forEach(function (origin) { - if (origin.name == item.originName) { - var intVal; - intVal = origin.TryGetValueForItem(item, intVal); - if (intVal !== undefined) { - _this2.Add(item, intVal); - return; - } else { - throw "Could not add the item " + item + " to this list because it doesn't exist in the original list definition in ink."; - } - } - }); - - throw "Failed to add item to list because the item was from a new list definition that wasn't previously known to this list. Only items from previously known lists can be used, so that the int value can be found."; - } else { - var itemName = itemOrItemName; - - var foundListDef = null; - - this.origins.forEach(function (origin) { - if (origin.ContainsItemWithName(itemName)) { - if (foundListDef != null) { - throw "Could not add the item " + itemName + " to this list because it could come from either " + origin.name + " or " + foundListDef.name; - } else { - foundListDef = origin; - } - } - }); - - if (foundListDef == null) throw "Could not add the item " + itemName + " to this list because it isn't known to any list definitions previously associated with this list."; - - var item = new InkListItem(foundListDef.name, itemName); - var itemVal = foundListDef.ValueForItem(item); - this.Add(item, itemVal); - } - } - }, { - key: 'ContainsItemNamed', - value: function ContainsItemNamed(itemName) { - var contains = false; - this.forEach(function (itemWithValue) { - if (itemWithValue.Key.itemName == itemName) contains = true; - }); - return contains; - } - }, { - key: 'ContainsKey', - value: function ContainsKey(key) { - return key in this._values; - } - }, { - key: 'Add', - value: function Add(key, value) { - this._keys[key] = key; - this._values[key] = value; - } - }, { - key: 'Remove', - value: function Remove(key) { - delete this._values[key]; - delete this._keys[key]; - } - }, { - key: 'SetInitialOriginName', - value: function SetInitialOriginName(initialOriginName) { - this._originNames = [initialOriginName]; - } - }, { - key: 'SetInitialOriginNames', - value: function SetInitialOriginNames(initialOriginNames) { - if (initialOriginNames == null) this._originNames = null;else this._originNames = initialOriginNames.slice(); //store a copy - } - }, { - key: 'Union', - value: function Union(otherList) { - var union = new InkList(this); - otherList.forEach(function (kv) { - union.Add(kv.Key, kv.Value); - }); - return union; - } - }, { - key: 'Intersect', - value: function Intersect(otherList) { - var intersection = new InkList(); - this.forEach(function (kv) { - if (otherList.ContainsKey(kv.Key)) intersection.Add(kv.Key, kv.Value); - }); - return intersection; - } - }, { - key: 'Without', - value: function Without(listToRemove) { - var result = new InkList(this); - listToRemove.forEach(function (kv) { - result.Remove(kv.Key); - }); - return result; - } - }, { - key: 'Contains', - value: function Contains(otherList) { - var _this3 = this; - - var contains = true; - otherList.forEach(function (kv) { - if (!_this3.ContainsKey(kv.Key)) contains = false; - }); - return contains; - } - }, { - key: 'GreaterThan', - value: function GreaterThan(otherList) { - if (this.Count == 0) return false; - if (otherList.Count == 0) return true; - - // All greater - return this.minItem.Value > otherList.maxItem.Value; - } - }, { - key: 'GreaterThanOrEquals', - value: function GreaterThanOrEquals(otherList) { - if (this.Count == 0) return false; - if (otherList.Count == 0) return true; - - return this.minItem.Value >= otherList.minItem.Value && this.maxItem.Value >= otherList.maxItem.Value; - } - }, { - key: 'LessThan', - value: function LessThan(otherList) { - if (otherList.Count == 0) return false; - if (this.Count == 0) return true; - - return this.maxItem.Value < otherList.minItem.Value; - } - }, { - key: 'LessThanOrEquals', - value: function LessThanOrEquals(otherList) { - if (otherList.Count == 0) return false; - if (this.Count == 0) return true; - - return this.maxItem.Value <= otherList.maxItem.Value && this.minItem.Value <= otherList.minItem.Value; - } - }, { - key: 'MaxAsList', - value: function MaxAsList() { - if (this.Count > 0) return new InkList(this.maxItem);else return new InkList(); - } - }, { - key: 'MinAsList', - value: function MinAsList() { - if (this.Count > 0) return new InkList(this.minItem);else return new InkList(); - } - }, { - key: 'Equals', - value: function Equals(other) { - // var otherInkList = other as InkList; - var otherInkList = other; - if (otherInkList instanceof InkList === false) return false; - if (otherInkList.Count != this.Count) return false; - - var equals = true; - this.forEach(function (kv) { - if (!otherInkList.ContainsKey(kv.Key)) equals = false; - }); - - return equals; - } - //GetHashCode not implemented - - }, { - key: 'toString', - value: function toString() { - var ordered = []; - this.forEach(function (kv) { - ordered.push(kv); - }); - ordered = ordered.sort(function (a, b) { - return a.Value === b.Value ? 0 : a.Value > b.Value ? 1 : -1; - }); - - var sb = new StringBuilder(); - for (var i = 0; i < ordered.length; i++) { - if (i > 0) sb.Append(", "); - - var item = ordered[i].Key; - sb.Append(item.itemName); - } - - return sb.toString(); - } - //casting a InkList to a Number, for somereason, actually gives a number. This messes up the type detection when creating a Value from a InkList. Returning NaN here prevents that. - - }, { - key: 'valueOf', - value: function valueOf() { - return NaN; - } - }, { - key: 'Count', - get: function get$$1() { - return Object.keys(this._values).length; - } - }, { - key: 'originOfMaxItem', - get: function get$$1() { - if (this.origins == null) return null; - - var maxOriginName = this.maxItem.Key.originName; - var result = null; - this.origins.every(function (origin) { - if (origin.name == maxOriginName) { - result = origin; - return false; - } else return true; - }); - - return result; - } - }, { - key: 'originNames', - get: function get$$1() { - var _this4 = this; - - if (this.Count > 0) { - if (this._originNames == null && this.Count > 0) this._originNames = [];else this._originNames.length = 0; - - this.forEach(function (itemAndValue) { - _this4._originNames.push(itemAndValue.Key.originName); - }); - } - - return this._originNames; - } - }, { - key: 'maxItem', - get: function get$$1() { - var max = { - Key: null, - Value: null - }; - this.forEach(function (kv) { - if (max.Key === null || kv.Value > max.Value) max = kv; - }); - - return max; - } - }, { - key: 'minItem', - get: function get$$1() { - var min = { - Key: null, - Value: null - }; - this.forEach(function (kv) { - if (min.Key === null || kv.Value < min.Value) min = kv; - }); - - return min; - } - }, { - key: 'inverse', - get: function get$$1() { - var _this5 = this; - - var list = new InkList(); - if (this.origins != null) { - this.origins.forEach(function (origin) { - origin.items.forEach(function (itemAndValue) { - if (!_this5.ContainsKey(itemAndValue.Key)) list.Add(itemAndValue.Key, itemAndValue.Value); - }); - }); - } - return list; - } - }, { - key: 'all', - get: function get$$1() { - var list = new InkList(); - if (this.origins != null) { - this.origins.forEach(function (origin) { - origin.items.forEach(function (itemAndValue) { - list.Add(itemAndValue.Key, itemAndValue.Value); - }); - }); - } - return list; - } - }]); - return InkList; - }(); - - var ValueType = { - // Used in coersion - Int: 0, - Float: 1, - List: 2, - String: 3, - - // Not used for coersion described above - DivertTarget: 4, - VariablePointer: 5 - }; - - var AbstractValue = function (_InkObject) { - inherits(AbstractValue, _InkObject); - - function AbstractValue(val) { - classCallCheck(this, AbstractValue); - - var _this = possibleConstructorReturn(this, (AbstractValue.__proto__ || Object.getPrototypeOf(AbstractValue)).call(this)); - - _this._valueType; - _this._isTruthy; - _this._valueObject; - return _this; - } - - createClass(AbstractValue, [{ - key: 'Cast', - value: function Cast(newType) { - throw "Trying to casting an AbstractValue"; - } - }, { - key: 'Copy', - value: function Copy(val) { - return AbstractValue.Create(val); - } - }, { - key: 'valueType', - get: function get$$1() { - return this._valueType; - } - }, { - key: 'isTruthy', - get: function get$$1() { - return this._isTruthy; - } - }, { - key: 'valueObject', - get: function get$$1() { - return this._valueObject; - } - }], [{ - key: 'Create', - value: function Create(val) { - // Implicitly convert bools into ints - if (typeof val === 'boolean') { - var b = !!val; - val = b ? 1 : 0; - } - - if (Number.isInteger(Number(val))) { - return new IntValue(val); - } else if (!isNaN(val)) { - return new FloatValue(val); - } else if (typeof val === 'string') { - return new StringValue(val); - } else if (val instanceof Path$1) { - return new DivertTargetValue(val); - } else if (val instanceof InkList) { - return new ListValue(val); - } - - return null; - } - }]); - return AbstractValue; - }(Object$1); - - var Value = function (_AbstractValue) { - inherits(Value, _AbstractValue); - - function Value(val) { - classCallCheck(this, Value); - - var _this2 = possibleConstructorReturn(this, (Value.__proto__ || Object.getPrototypeOf(Value)).call(this)); - - _this2.value = val; - return _this2; - } - - createClass(Value, [{ - key: 'toString', - value: function toString() { - return this.value.toString(); - } - }, { - key: 'value', - get: function get$$1() { - return this._value; - }, - set: function set$$1(value) { - this._value = value; - } - }, { - key: 'valueObject', - get: function get$$1() { - return this.value; - } - }]); - return Value; - }(AbstractValue); - - var IntValue = function (_Value) { - inherits(IntValue, _Value); - - function IntValue(val) { - classCallCheck(this, IntValue); - - var _this3 = possibleConstructorReturn(this, (IntValue.__proto__ || Object.getPrototypeOf(IntValue)).call(this, val || 0)); - - _this3._valueType = ValueType.Int; - return _this3; - } - - createClass(IntValue, [{ - key: 'Cast', - value: function Cast(newType) { - if (newType == this.valueType) { - return this; - } - - if (newType == ValueType.Float) { - return new FloatValue(parseFloat(this.value)); - } - - if (newType == ValueType.String) { - return new StringValue("" + this.value); - } - - throw "Unexpected type cast of Value to new ValueType"; - } - }, { - key: 'isTruthy', - get: function get$$1() { - return this.value != 0; - } - }, { - key: 'valueType', - get: function get$$1() { - return ValueType.Int; - } - }]); - return IntValue; - }(Value); - - var FloatValue = function (_Value2) { - inherits(FloatValue, _Value2); - - function FloatValue(val) { - classCallCheck(this, FloatValue); - - var _this4 = possibleConstructorReturn(this, (FloatValue.__proto__ || Object.getPrototypeOf(FloatValue)).call(this, val || 0.0)); - - _this4._valueType = ValueType.Float; - return _this4; - } - - createClass(FloatValue, [{ - key: 'Cast', - value: function Cast(newType) { - if (newType == this.valueType) { - return this; - } - - if (newType == ValueType.Int) { - return new IntValue(parseInt(this.value)); - } - - if (newType == ValueType.String) { - return new StringValue("" + this.value); - } - - throw "Unexpected type cast of Value to new ValueType"; - } - }, { - key: 'isTruthy', - get: function get$$1() { - return this._value != 0.0; - } - }, { - key: 'valueType', - get: function get$$1() { - return ValueType.Float; - } - }]); - return FloatValue; - }(Value); - - var StringValue = function (_Value3) { - inherits(StringValue, _Value3); - - function StringValue(val) { - classCallCheck(this, StringValue); - - var _this5 = possibleConstructorReturn(this, (StringValue.__proto__ || Object.getPrototypeOf(StringValue)).call(this, val || '')); - - _this5._valueType = ValueType.String; - - _this5._isNewline = _this5.value == "\n"; - _this5._isInlineWhitespace = true; - - _this5.value.split().every(function (c) { - if (c != ' ' && c != '\t') { - _this5._isInlineWhitespace = false; - return false; - } - - return true; - }); - return _this5; - } - - createClass(StringValue, [{ - key: 'Cast', - value: function Cast(newType) { - if (newType == this.valueType) { - return this; - } - - if (newType == ValueType.Int) { - - var parsedInt; - if (parsedInt = parseInt(value)) { - return new IntValue(parsedInt); - } else { - return null; - } - } - - if (newType == ValueType.Float) { - var parsedFloat; - if (parsedFloat = parsedFloat(value)) { - return new FloatValue(parsedFloat); - } else { - return null; - } - } - - throw "Unexpected type cast of Value to new ValueType"; - } - }, { - key: 'valueType', - get: function get$$1() { - return ValueType.String; - } - }, { - key: 'isTruthy', - get: function get$$1() { - return this.value.length > 0; - } - }, { - key: 'isNewline', - get: function get$$1() { - return this._isNewline; - } - }, { - key: 'isInlineWhitespace', - get: function get$$1() { - return this._isInlineWhitespace; - } - }, { - key: 'isNonWhitespace', - get: function get$$1() { - return !this.isNewline && !this.isInlineWhitespace; - } - }]); - return StringValue; - }(Value); - - var DivertTargetValue = function (_Value4) { - inherits(DivertTargetValue, _Value4); - - function DivertTargetValue(targetPath) { - classCallCheck(this, DivertTargetValue); - - var _this6 = possibleConstructorReturn(this, (DivertTargetValue.__proto__ || Object.getPrototypeOf(DivertTargetValue)).call(this, targetPath)); - - _this6._valueType = ValueType.DivertTarget; - return _this6; - } - - createClass(DivertTargetValue, [{ - key: 'Cast', - value: function Cast(newType) { - if (newType == this.valueType) return this; - - throw "Unexpected type cast of Value to new ValueType"; - } - }, { - key: 'toString', - value: function toString() { - return "DivertTargetValue(" + this.targetPath + ")"; - } - }, { - key: 'targetPath', - get: function get$$1() { - return this.value; - }, - set: function set$$1(value) { - this.value = value; - } - }, { - key: 'isTruthy', - get: function get$$1() { - throw "Shouldn't be checking the truthiness of a divert target"; - } - }]); - return DivertTargetValue; - }(Value); - - var VariablePointerValue = function (_Value5) { - inherits(VariablePointerValue, _Value5); - - function VariablePointerValue(variableName, contextIndex) { - classCallCheck(this, VariablePointerValue); - - var _this7 = possibleConstructorReturn(this, (VariablePointerValue.__proto__ || Object.getPrototypeOf(VariablePointerValue)).call(this, variableName)); - - _this7._valueType = ValueType.VariablePointer; - _this7.contextIndex = typeof contextIndex !== 'undefined' ? contextIndex : -1; - return _this7; - } - - createClass(VariablePointerValue, [{ - key: 'Cast', - value: function Cast(newType) { - if (newType == this.valueType) return this; - - throw "Unexpected type cast of Value to new ValueType"; - } - }, { - key: 'toString', - value: function toString() { - return "VariablePointerValue(" + this.variableName + ")"; - } - }, { - key: 'Copy', - value: function Copy() { - return new VariablePointerValue(this.variableName, this.contextIndex); - } - }, { - key: 'variableName', - get: function get$$1() { - return this.value; - }, - set: function set$$1(value) { - this.value = value; - } - }, { - key: 'isTruthy', - get: function get$$1() { - throw "Shouldn't be checking the truthiness of a variable pointer"; - } - }]); - return VariablePointerValue; - }(Value); - - var ListValue = function (_Value6) { - inherits(ListValue, _Value6); - createClass(ListValue, [{ - key: 'Cast', - value: function Cast(newType) { - if (newType == ValueType.Int) { - var max = this.value.maxItem; - if (max.Key.isNull) return new IntValue(0);else return new IntValue(max.Value); - } else if (newType == ValueType.Float) { - var max = this.value.maxItem; - if (max.Key.isNull) return new FloatValue(0.0);else return new FloatValue(parseFloat(max.Value)); - } else if (newType == ValueType.String) { - var max = value.maxItem; - if (max.Key.isNull) return new StringValue("");else { - return new StringValue(max.Key.toString()); - } - } - - if (newType == this.valueType) return this; - - throw "Unexpected type cast of Value to new ValueType"; - } - }, { - key: 'valueType', - get: function get$$1() { - return ValueType.List; - } - }, { - key: 'isTruthy', - get: function get$$1() { - var isTruthy = false; - this.value.forEach(function (kv) { - var listItemIntValue = kv.Value; - if (listItemIntValue != 0) isTruthy = true; - }); - return isTruthy; - } - }]); - - function ListValue(listOrSingleItem, singleValue) { - classCallCheck(this, ListValue); - - var _this8 = possibleConstructorReturn(this, (ListValue.__proto__ || Object.getPrototypeOf(ListValue)).call(this, null)); - - _this8._valueType = ValueType.List; - - if (listOrSingleItem instanceof InkList) { - _this8.value = new InkList(listOrSingleItem); - } else if (listOrSingleItem !== undefined && singleValue !== undefined) { - _this8.value = new InkList({ - Key: listOrSingleItem, - Value: singleValue - }); - } else { - _this8.value = new InkList(); - } - return _this8; - } - - createClass(ListValue, null, [{ - key: 'RetainListOriginsForAssignment', - value: function RetainListOriginsForAssignment(oldValue, newValue) { - // var oldList = oldValue as ListValue; - var oldList = oldValue; - // var newList = newValue as ListValue; - var newList = newValue; - - // When assigning the emtpy list, try to retain any initial origin names - if (oldList instanceof ListValue && newList instanceof ListValue && newList.value.Count == 0) newList.value.SetInitialOriginNames(oldList.value.originNames); - } - }]); - return ListValue; - }(Value); - - var StoryException = function (_Error) { - inherits(StoryException, _Error); - - function StoryException(message) { - classCallCheck(this, StoryException); - - var _this = possibleConstructorReturn(this, (StoryException.__proto__ || Object.getPrototypeOf(StoryException)).call(this, message)); - - _this.message = message; - _this.name = 'StoryException'; - return _this; - } - - return StoryException; - }(Error); - - var Container = function (_InkObject) { - inherits(Container, _InkObject); - - //also implements INamedContent. Not sure how to do it cleanly in JS. - function Container() { - classCallCheck(this, Container); - - var _this = possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this)); - - _this.name = ''; - - _this._content = []; - _this.namedContent = {}; - - _this.visitsShouldBeCounted = false; - _this.turnIndexShouldBeCounted = false; - _this.countingAtStartOnly = false; - - _this.CountFlags = { - Visits: 1, - Turns: 2, - CountStartOnly: 4 - }; - - _this._pathToFirstLeafContent = null; - return _this; - } - - createClass(Container, [{ - key: 'AddContent', - value: function AddContent(contentObj) { - var _this2 = this; - - if (contentObj instanceof Array) { - contentObj.forEach(function (c) { - _this2.AddContent(c); - }); - } else { - this._content.push(contentObj); - - if (contentObj.parent) { - throw "content is already in " + contentObj.parent; - } - - contentObj.parent = this; - - this.TryAddNamedContent(contentObj); - } - } - }, { - key: 'TryAddNamedContent', - value: function TryAddNamedContent(contentObj) { - //so here, in the reference implementation, contentObj is casted to an INamedContent - //but here we use js-style duck typing: if it implements the same props as the interface, we treat it as valid - if (contentObj.hasValidName && contentObj.name) { - this.AddToNamedContentOnly(contentObj); - } - } - }, { - key: 'AddToNamedContentOnly', - value: function AddToNamedContentOnly(namedContentObj) { - if (namedContentObj instanceof Object$1 === false) console.warn("Can only add Runtime.Objects to a Runtime.Container"); - namedContentObj.parent = this; - - this.namedContent[namedContentObj.name] = namedContentObj; - } - }, { - key: 'ContentAtPath', - value: function ContentAtPath(path, partialPathLength) { - partialPathLength = typeof partialPathLength !== 'undefined' ? partialPathLength : path.components.length; - - var currentContainer = this; - var currentObj = this; - - for (var i = 0; i < partialPathLength; ++i) { - var comp = path.components[i]; - if (!(currentContainer instanceof Container)) throw "Path continued, but previous object wasn't a container: " + currentObj; - - currentObj = currentContainer.ContentWithPathComponent(comp); - // currentContainer = currentObj as Container; - currentContainer = currentObj; - } - - return currentObj; - } - }, { - key: 'InsertContent', - value: function InsertContent(contentObj, index) { - this.content[i] = contentObj; - - if (contentObj.parent) { - throw "content is already in " + contentObj.parent; - } - - contentObj.parent = this; - - this.TryAddNamedContent(contentObj); - } - }, { - key: 'AddContentsOfContainer', - value: function AddContentsOfContainer(otherContainer) { - var _this3 = this; - - this.content = this.content.concat(otherContainer.content); - - otherContainer.content.forEach(function (obj) { - obj.parent = _this3; - _this3.TryAddNamedContent(obj); - }); - } - }, { - key: 'ContentWithPathComponent', - value: function ContentWithPathComponent(component) { - if (component.isIndex) { - - if (component.index >= 0 && component.index < this.content.length) { - return this.content[component.index]; - } - - // When path is out of range, quietly return nil - // (useful as we step/increment forwards through content) - else { - return null; - } - } else if (component.isParent) { - return this.parent; - } else { - var foundContent = null; - if (foundContent = this.namedContent[component.name]) { - return foundContent; - } else { - throw new StoryException("Content '" + component.name + "' not found at path: '" + this.path + "'"); - } - } - } - }, { - key: 'BuildStringOfHierarchy', - value: function BuildStringOfHierarchy(sb, indentation, pointedObj) { - if (arguments.length == 0) { - var sb = new StringBuilder(); - this.BuildStringOfHierarchy(sb, 0, null); - return sb.toString(); - } - - function appendIndentation() { - var spacesPerIndent = 4; - for (var i = 0; i < spacesPerIndent * indentation; ++i) { - sb.Append(" "); - } - } - - appendIndentation(); - sb.Append("["); - - if (this.hasValidName) { - sb.AppendFormat(" ({0})", this.name); - } - - if (this == pointedObj) { - sb.Append(" <---"); - } - - sb.AppendLine(); - - indentation++; - - for (var i = 0; i < this.content.length; ++i) { - - var obj = this.content[i]; - - if (obj instanceof Container) { - - var container = obj; - - container.BuildStringOfHierarchy(sb, indentation, pointedObj); - } else { - appendIndentation(); - if (obj instanceof StringValue) { - sb.Append("\""); - sb.Append(obj.toString().replace("\n", "\\n")); - sb.Append("\""); - } else { - sb.Append(obj.toString()); - } - } - - if (i != this.content.length - 1) { - sb.Append(","); - } - - if (!(obj instanceof Container) && obj == pointedObj) { - sb.Append(" <---"); - } - - sb.AppendLine(); - } - - var onlyNamed = {}; - - for (var key in this.namedContent) { - if (this.content.indexOf(this.namedContent[key]) >= 0) { - continue; - } else { - onlyNamed[key] = this.namedContent[key]; - } - } - - if (Object.keys(onlyNamed).length > 0) { - appendIndentation(); - sb.AppendLine("-- named: --"); - - for (var key in onlyNamed) { - if (!(onlyNamed[key] instanceof Container)) console.warn("Can only print out named Containers"); - - var container = onlyNamed[key]; - container.BuildStringOfHierarchy(sb, indentation, pointedObj); - sb.Append("\n"); - } - } - - indentation--; - - appendIndentation(); - sb.Append("]"); - } - }, { - key: 'hasValidName', - get: function get$$1() { - return this.name != null && this.name.length > 0; - } - }, { - key: 'content', - get: function get$$1() { - return this._content; - }, - set: function set$$1(value) { - this.AddContent(value); - } - }, { - key: 'namedOnlyContent', - get: function get$$1() { - var namedOnlyContentDict = {}; - - for (var key in this.namedContent) { - namedOnlyContentDict[key] = this.namedContent[key]; - } - - this.content.forEach(function (c) { - // var named = c as INamedContent; - var named = c; - if (named.name && named.hasValidName) { - delete namedOnlyContentDict[named.name]; - } - }); - - if (Object.keys(namedOnlyContentDict).length == 0) namedOnlyContentDict = null; - - return namedOnlyContentDict; - }, - set: function set$$1(value) { - var existingNamedOnly = this.namedOnlyContent; - if (existingNamedOnly != null) { - for (var key in existingNamedOnly) { - delete this.namedContent[key]; - } - } - - if (value == null) return; - - for (var key in value) { - // var named = kvPair.Value as INamedContent; - var named = value[key]; - if (named.name && typeof named.hasValidName !== 'undefined') this.AddToNamedContentOnly(named); - } - } - }, { - key: 'countFlags', - get: function get$$1() { - var flags = 0; - if (this.visitsShouldBeCounted) flags |= this.CountFlags.Visits; - if (this.turnIndexShouldBeCounted) flags |= this.CountFlags.Turns; - if (this.countingAtStartOnly) flags |= this.CountFlags.CountStartOnly; - - // If we're only storing CountStartOnly, it serves no purpose, - // since it's dependent on the other two to be used at all. - // (e.g. for setting the fact that *if* a gather or choice's - // content is counted, then is should only be counter at the start) - // So this is just an optimisation for storage. - if (flags == this.CountFlags.CountStartOnly) { - flags = 0; - } - - return flags; - }, - set: function set$$1(value) { - var flag = value; - if ((flag & this.CountFlags.Visits) > 0) this.visitsShouldBeCounted = true; - if ((flag & this.CountFlags.Turns) > 0) this.turnIndexShouldBeCounted = true; - if ((flag & this.CountFlags.CountStartOnly) > 0) this.countingAtStartOnly = true; - } - }, { - key: 'pathToFirstLeafContent', - get: function get$$1() { - if (this._pathToFirstLeafContent == null) this._pathToFirstLeafContent = this.path.PathByAppendingPath(this.internalPathToFirstLeafContent); - - return this._pathToFirstLeafContent; - } - }, { - key: 'internalPathToFirstLeafContent', - get: function get$$1() { - var path = new Path(); - var container = this; - while (container instanceof Container) { - if (container.content.length > 0) { - path.components.push(new Path.Component(0)); - // container = container.content [0] as Container; - container = container.content[0]; - } - } - return path; - } - }]); - return Container; - }(Object$1); - - var Glue = function (_InkObject) { - inherits(Glue, _InkObject); - - function Glue(type) { - classCallCheck(this, Glue); - - var _this = possibleConstructorReturn(this, (Glue.__proto__ || Object.getPrototypeOf(Glue)).call(this)); - - _this.glueType = type; - return _this; - } - - createClass(Glue, [{ - key: "toString", - value: function toString() { - switch (this.glueType) { - case GlueType.Bidirectional: - return "BidirGlue"; - case GlueType.Left: - return "LeftGlue"; - case GlueType.Right: - return "RightGlue"; - } - - return "UnexpectedGlueType"; - } - }, { - key: "isLeft", - get: function get$$1() { - return this.glueType == GlueType.Left; - } - }, { - key: "isBi", - get: function get$$1() { - return this.glueType == GlueType.Bidirectional; - } - }, { - key: "isRight", - get: function get$$1() { - return this.glueType == GlueType.Right; - } - }]); - return Glue; - }(Object$1); - - var GlueType = { - Bidirectional: 0, - Left: 1, - Right: 2 - }; - - var ControlCommand = function (_InkObject) { - inherits(ControlCommand, _InkObject); - - function ControlCommand(commandType) { - classCallCheck(this, ControlCommand); - - var _this = possibleConstructorReturn(this, (ControlCommand.__proto__ || Object.getPrototypeOf(ControlCommand)).call(this)); - - _this._commandType = typeof commandType != 'undefined' ? commandType : CommandType.NotSet; - return _this; - } - - createClass(ControlCommand, [{ - key: 'copy', - value: function copy() { - return new ControlCommand(this.commandType); - } - }, { - key: 'toString', - value: function toString() { - return this.commandType.toString(); - } - }, { - key: 'commandType', - get: function get$$1() { - return this._commandType; - } - }], [{ - key: 'EvalStart', - value: function EvalStart() { - return new ControlCommand(CommandType.EvalStart); - } - }, { - key: 'EvalOutput', - value: function EvalOutput() { - return new ControlCommand(CommandType.EvalOutput); - } - }, { - key: 'EvalEnd', - value: function EvalEnd() { - return new ControlCommand(CommandType.EvalEnd); - } - }, { - key: 'Duplicate', - value: function Duplicate() { - return new ControlCommand(CommandType.Duplicate); - } - }, { - key: 'PopEvaluatedValue', - value: function PopEvaluatedValue() { - return new ControlCommand(CommandType.PopEvaluatedValue); - } - }, { - key: 'PopFunction', - value: function PopFunction() { - return new ControlCommand(CommandType.PopFunction); - } - }, { - key: 'PopTunnel', - value: function PopTunnel() { - return new ControlCommand(CommandType.PopTunnel); - } - }, { - key: 'BeginString', - value: function BeginString() { - return new ControlCommand(CommandType.BeginString); - } - }, { - key: 'EndString', - value: function EndString() { - return new ControlCommand(CommandType.EndString); - } - }, { - key: 'NoOp', - value: function NoOp() { - return new ControlCommand(CommandType.NoOp); - } - }, { - key: 'ChoiceCount', - value: function ChoiceCount() { - return new ControlCommand(CommandType.ChoiceCount); - } - }, { - key: 'TurnsSince', - value: function TurnsSince() { - return new ControlCommand(CommandType.TurnsSince); - } - }, { - key: 'ReadCount', - value: function ReadCount() { - return new ControlCommand(CommandType.ReadCount); - } - }, { - key: 'Random', - value: function Random() { - return new ControlCommand(CommandType.Random); - } - }, { - key: 'SeedRandom', - value: function SeedRandom() { - return new ControlCommand(CommandType.SeedRandom); - } - }, { - key: 'VisitIndex', - value: function VisitIndex() { - return new ControlCommand(CommandType.VisitIndex); - } - }, { - key: 'SequenceShuffleIndex', - value: function SequenceShuffleIndex() { - return new ControlCommand(CommandType.SequenceShuffleIndex); - } - }, { - key: 'StartThread', - value: function StartThread() { - return new ControlCommand(CommandType.StartThread); - } - }, { - key: 'Done', - value: function Done() { - return new ControlCommand(CommandType.Done); - } - }, { - key: 'End', - value: function End() { - return new ControlCommand(CommandType.End); - } - }, { - key: 'ListFromInt', - value: function ListFromInt() { - return new ControlCommand(CommandType.ListFromInt); - } - }, { - key: 'ListRange', - value: function ListRange() { - return new ControlCommand(CommandType.ListRange); - } - }]); - return ControlCommand; - }(Object$1); - - var CommandType = { - NotSet: -1, - EvalStart: 0, - EvalOutput: 1, - EvalEnd: 2, - Duplicate: 3, - PopEvaluatedValue: 4, - PopFunction: 5, - PopTunnel: 6, - BeginString: 7, - EndString: 8, - NoOp: 9, - ChoiceCount: 10, - TurnsSince: 11, - Random: 12, - SeedRandom: 13, - VisitIndex: 14, - SequenceShuffleIndex: 15, - StartThread: 16, - Done: 17, - End: 18, - ListFromInt: 19, - ListRange: 20, - ReadCount: 21 - }; - CommandType.TOTAL_VALUES = Object.keys(CommandType).length - 1; //-1 because NotSet shoudn't count - ControlCommand.CommandType = CommandType; - - var PushPopType = { - Tunnel: 0, - Function: 1 - }; - - var Divert = function (_InkObject) { - inherits(Divert, _InkObject); - - function Divert(stackPushType) { - classCallCheck(this, Divert); - - var _this = possibleConstructorReturn(this, (Divert.__proto__ || Object.getPrototypeOf(Divert)).call(this)); - - _this._targetPath; - _this._targetContent; - - _this.variableDivertName; - _this.pushesToStack; - _this.stackPushType; - - _this.isExternal; - _this.isConditional; - _this.externalArgs; - - //actual constructor - _this.pushesToStack = false; - if (stackPushType) { - _this.pushesToStack = true; - _this.stackPushType = stackPushType; - } - return _this; - } - - createClass(Divert, [{ - key: 'Equals', - value: function Equals(obj) { - // var otherDivert = obj as Divert; - var otherDivert = obj; - if (otherDivert instanceof Divert) { - if (this.hasVariableTarget == otherDivert.hasVariableTarget) { - if (this.hasVariableTarget) { - return this.variableDivertName == otherDivert.variableDivertName; - } else { - return this.targetPath.Equals(otherDivert.targetPath); - } - } - } - return false; - } - }, { - key: 'toString', - value: function toString() { - if (this.hasVariableTarget) { - return "Divert(variable: " + this.variableDivertName + ")"; - } else if (this.targetPath == null) { - return "Divert(null)"; - } else { - - var sb = new StringBuilder(); - - var targetStr = this.targetPath.toString(); - // int? targetLineNum = DebugLineNumberOfPath (targetPath); - var targetLineNum = null; - if (targetLineNum != null) { - targetStr = "line " + targetLineNum; - } - - sb.Append("Divert"); - if (this.pushesToStack) { - if (this.stackPushType == PushPopType.Function) { - sb.Append(" function"); - } else { - sb.Append(" tunnel"); - } - } - - sb.Append(" ("); - sb.Append(targetStr); - sb.Append(")"); - - return sb.toString(); - } - } - }, { - key: 'targetPath', - get: function get$$1() { - // Resolve any relative paths to global ones as we come across them - if (this._targetPath != null && this._targetPath.isRelative) { - var targetObj = this.targetContent; - if (targetObj) { - this._targetPath = targetObj.path; - } - } - - return this._targetPath; - }, - set: function set$$1(value) { - this._targetPath = value; - this._targetContent = null; - } - }, { - key: 'targetContent', - get: function get$$1() { - if (this._targetContent == null) { - this._targetContent = this.ResolvePath(this._targetPath); - } - - return this._targetContent; - } - }, { - key: 'targetPathString', - get: function get$$1() { - if (this.targetPath == null) return null; - - return this.CompactPathString(this.targetPath); - }, - set: function set$$1(value) { - if (value == null) { - this.targetPath = null; - } else { - this.targetPath = new Path$1(value); - } - } - }, { - key: 'hasVariableTarget', - get: function get$$1() { - return this.variableDivertName != null; - } - }]); - return Divert; - }(Object$1); - - var ChoicePoint = function (_InkObject) { - inherits(ChoicePoint, _InkObject); - - function ChoicePoint(onceOnly) { - classCallCheck(this, ChoicePoint); - - var _this = possibleConstructorReturn(this, (ChoicePoint.__proto__ || Object.getPrototypeOf(ChoicePoint)).call(this)); - - _this._pathOnChoice; - _this.hasCondition; - _this.hasStartContent; - _this.hasChoiceOnlyContent; - _this.onceOnly; - _this.isInvisibleDefault; - - _this.onceOnly = !!onceOnly; - return _this; - } - - createClass(ChoicePoint, [{ - key: 'toString', - value: function toString() { - // int? targetLineNum = DebugLineNumberOfPath (pathOnChoice); - var targetLineNum = null; - var targetString = this.pathOnChoice.toString(); - - if (targetLineNum != null) { - targetString = " line " + targetLineNum; - } - - return "Choice: -> " + targetString; - } - }, { - key: 'pathOnChoice', - get: function get$$1() { - if (this._pathOnChoice != null && this._pathOnChoice.isRelative) { - var choiceTargetObj = this.choiceTarget; - if (choiceTargetObj) { - this._pathOnChoice = choiceTargetObj.path; - } - } - return this._pathOnChoice; - }, - set: function set$$1(value) { - this._pathOnChoice = value; - } - }, { - key: 'choiceTarget', - get: function get$$1() { - //return this.ResolvePath (_pathOnChoice) as Container; - return this.ResolvePath(this._pathOnChoice); - } - }, { - key: 'pathStringOnChoice', - get: function get$$1() { - return this.CompactPathString(this.pathOnChoice); - }, - set: function set$$1(value) { - this.pathOnChoice = new Path$1(value); - } - }, { - key: 'flags', - get: function get$$1() { - var flags = 0; - if (this.hasCondition) flags |= 1; - if (this.hasStartContent) flags |= 2; - if (this.hasChoiceOnlyContent) flags |= 4; - if (this.isInvisibleDefault) flags |= 8; - if (this.onceOnly) flags |= 16; - return flags; - }, - set: function set$$1(value) { - this.hasCondition = (value & 1) > 0; - this.hasStartContent = (value & 2) > 0; - this.hasChoiceOnlyContent = (value & 4) > 0; - this.isInvisibleDefault = (value & 8) > 0; - this.onceOnly = (value & 16) > 0; - } - }]); - return ChoicePoint; - }(Object$1); - - var VariableReference = function (_InkObject) { - inherits(VariableReference, _InkObject); - - function VariableReference(name) { - classCallCheck(this, VariableReference); - - var _this = possibleConstructorReturn(this, (VariableReference.__proto__ || Object.getPrototypeOf(VariableReference)).call(this)); - - _this.name = name; - _this.pathForCount; - return _this; - } - - createClass(VariableReference, [{ - key: 'toString', - value: function toString() { - if (this.name != null) { - return "var(" + this.name + ")"; - } else { - var pathStr = this.pathStringForCount; - return "read_count(" + pathStr + ")"; - } - } - }, { - key: 'containerForCount', - get: function get$$1() { - return this.ResolvePath(this.pathForCount); - } - }, { - key: 'pathStringForCount', - get: function get$$1() { - if (this.pathForCount == null) return null; - - return this.CompactPathString(this.pathForCount); - }, - set: function set$$1(value) { - if (value == null) this.pathForCount = null;else this.pathForCount = new Path$1(value); - } - }]); - return VariableReference; - }(Object$1); - - var VariableAssignment = function (_InkObject) { - inherits(VariableAssignment, _InkObject); - - function VariableAssignment(variableName, isNewDeclaration) { - classCallCheck(this, VariableAssignment); - - var _this = possibleConstructorReturn(this, (VariableAssignment.__proto__ || Object.getPrototypeOf(VariableAssignment)).call(this)); - - _this._variableName = variableName || null; - _this._isNewDeclaration = !!isNewDeclaration; - _this.isGlobal; - return _this; - } - - createClass(VariableAssignment, [{ - key: "toString", - value: function toString() { - return "VarAssign to " + this.variableName; - } - }, { - key: "variableName", - get: function get$$1() { - return this._variableName; - } - }, { - key: "isNewDeclaration", - get: function get$$1() { - return this._isNewDeclaration; - } - }]); - return VariableAssignment; - }(Object$1); - - var Void = function (_InkObject) { - inherits(Void, _InkObject); - - function Void() { - classCallCheck(this, Void); - return possibleConstructorReturn(this, (Void.__proto__ || Object.getPrototypeOf(Void)).apply(this, arguments)); - } - - return Void; - }(Object$1); - - //misses delegates, probably the returns from function calls - var NativeFunctionCall = function (_InkObject) { - inherits(NativeFunctionCall, _InkObject); - - function NativeFunctionCall(name) { - classCallCheck(this, NativeFunctionCall); - - var _this = possibleConstructorReturn(this, (NativeFunctionCall.__proto__ || Object.getPrototypeOf(NativeFunctionCall)).call(this)); - - _this.name = name; - _this._numberOfParameters; - - _this._prototype; - _this._isPrototype; - _this._operationFuncs = null; - - NativeFunctionCall.GenerateNativeFunctionsIfNecessary(); - return _this; - } - - createClass(NativeFunctionCall, [{ - key: 'Call', - value: function Call(parameters) { - if (this._prototype) { - return this._prototype.Call(parameters); - } - - if (this.numberOfParameters != parameters.length) { - throw "Unexpected number of parameters"; - } - - var hasList = false; - parameters.forEach(function (p) { - if (p instanceof Void) throw new StoryException("Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?"); - if (p instanceof ListValue) hasList = true; - }); - - if (parameters.length == 2 && hasList) { - return this.CallBinaryListOperation(parameters); - } - - var coercedParams = this.CoerceValuesToSingleType(parameters); - var coercedType = coercedParams[0].valueType; - - //Originally CallType gets a type parameter that is used to do some casting, but we can do without. - if (coercedType == ValueType.Int) { - return this.CallType(coercedParams); - } else if (coercedType == ValueType.Float) { - return this.CallType(coercedParams); - } else if (coercedType == ValueType.String) { - return this.CallType(coercedParams); - } else if (coercedType == ValueType.DivertTarget) { - return this.CallType(coercedParams); - } else if (coercedType == ValueType.List) { - return this.CallType(coercedParams); - } - - return null; - } - }, { - key: 'CallType', - value: function CallType(parametersOfSingleType) { - var param1 = parametersOfSingleType[0]; - var valType = param1.valueType; - - var val1 = param1; - - var paramCount = parametersOfSingleType.length; - - if (paramCount == 2 || paramCount == 1) { - - var opForTypeObj = this._operationFuncs[valType]; - if (!opForTypeObj) { - throw new StoryException("Cannot perform operation '" + this.name + "' on " + valType); - } - - // Binary - if (paramCount == 2) { - var param2 = parametersOfSingleType[1]; - - var val2 = param2; - - var opForType = opForTypeObj; - - // Return value unknown until it's evaluated - var resultVal = opForType(val1.value, val2.value); - - return Value.Create(resultVal); - } - - // Unary - else { - - var opForType = opForTypeObj; - - var resultVal = opForType(val1.value); - - return Value.Create(resultVal); - } - } else { - throw "Unexpected number of parameters to NativeFunctionCall: " + parametersOfSingleType.length; - } - } - }, { - key: 'CallBinaryListOperation', - value: function CallBinaryListOperation(parameters) { - // List-Int addition/subtraction returns a List (e.g. "alpha" + 1 = "beta") - if ((this.name == "+" || this.name == "-") && parameters[0] instanceof ListValue && parameters[1] instanceof IntValue) return this.CallListIncrementOperation(parameters); - - // var v1 = parameters [0] as Value; - var v1 = parameters[0]; - // var v2 = parameters [1] as Value; - var v2 = parameters[1]; - - // And/or with any other type requires coerscion to bool (int) - if ((this.name == "&&" || this.name == "||") && (v1.valueType != ValueType.List || v2.valueType != ValueType.List)) { - // var op = _operationFuncs [ValueType.Int] as BinaryOp; - var op = this._operationFuncs[ValueType.Int]; - var result = op(v1.isTruthy ? 1 : 0, v2.isTruthy ? 1 : 0); - return new IntValue(result); - } - - // Normal (list • list) operation - if (v1.valueType == ValueType.List && v2.valueType == ValueType.List) return this.CallType([v1, v2]); - - throw new StoryException("Can not call use '" + this.name + "' operation on " + v1.valueType + " and " + v2.valueType); - } - }, { - key: 'CallListIncrementOperation', - value: function CallListIncrementOperation(listIntParams) { - var _this2 = this; - - var listVal = listIntParams[0]; - var intVal = listIntParams[1]; - - var resultInkList = new InkList(); - - listVal.value.forEach(function (listItemWithValue) { - var listItem = listItemWithValue.Key; - var listItemValue = listItemWithValue.Value; - - // Find + or - operation - var intOp = _this2._operationFuncs[ValueType.Int]; - - // Return value unknown until it's evaluated - var targetInt = intOp(listItemValue, intVal.value); - - // Find this item's origin (linear search should be ok, should be short haha) - var itemOrigin = null; - listVal.value.origins.forEach(function (origin) { - if (origin.name == listItem.originName) { - itemOrigin = origin; - return false; - } - }); - if (itemOrigin != null) { - var incrementedItem = itemOrigin.TryGetItemWithValue(targetInt); - if (incrementedItem.exists) resultInkList.Add(incrementedItem.item, targetInt); - } - }); - - return new ListValue(resultInkList); - } - }, { - key: 'CoerceValuesToSingleType', - value: function CoerceValuesToSingleType(parametersIn) { - var valType = ValueType.Int; - - var specialCaseList = null; - - // Find out what the output type is - // "higher level" types infect both so that binary operations - // use the same type on both sides. e.g. binary operation of - // int and float causes the int to be casted to a float. - parametersIn.forEach(function (obj) { - var val = obj; - if (val.valueType > valType) { - valType = val.valueType; - } - - if (val.valueType == ValueType.List) { - // specialCaseList = val as ListValue; - specialCaseList = val; - } - }); - - // Coerce to this chosen type - var parametersOut = []; - - if (valType == ValueType.List) { - parametersIn.forEach(function (val) { - if (val.valueType == ValueType.List) { - parametersOut.push(val); - } else if (val.valueType == ValueType.Int) { - var intVal = parseInt(val.valueObject); - var list = specialCaseList.value.originOfMaxItem; - - var item = list.TryGetItemWithValue(intVal); - if (item.exists) { - var castedValue = new ListValue(item.item, intVal); - parametersOut.push(castedValue); - } else throw new StoryException("Could not find List item with the value " + intVal + " in " + list.name); - } else throw new StoryException("Cannot mix Lists and " + val.valueType + " values in this operation"); - }); - } - - // Normal Coercing (with standard casting) - else { - parametersIn.forEach(function (val) { - var castedValue = val.Cast(valType); - parametersOut.push(castedValue); - }); - } - - return parametersOut; - } - }, { - key: 'AddOpFuncForType', - value: function AddOpFuncForType(valType, op) { - if (this._operationFuncs == null) { - this._operationFuncs = {}; - } - - this._operationFuncs[valType] = op; - } - }, { - key: 'toString', - value: function toString() { - return "Native '" + this.name + "'"; - } - }, { - key: 'name', - get: function get$$1() { - return this._name; - }, - set: function set$$1(value) { - this._name = value; - if (!this._isPrototype) this._prototype = NativeFunctionCall._nativeFunctions[this._name]; - } - }, { - key: 'numberOfParameters', - get: function get$$1() { - if (this._prototype) { - return this._prototype.numberOfParameters; - } else { - return this._numberOfParameters; - } - }, - set: function set$$1(value) { - this._numberOfParameters = value; - } - }], [{ - key: 'internalConstructor', - value: function internalConstructor(name, numberOfParamters) { - var nativeFunc = new NativeFunctionCall(name); - nativeFunc._isPrototype = true; - nativeFunc.numberOfParameters = numberOfParamters; - return nativeFunc; - } - }, { - key: 'CallWithName', - value: function CallWithName(functionName) { - return new NativeFunctionCall(functionName); - } - }, { - key: 'CallExistsWithName', - value: function CallExistsWithName(functionName) { - this.GenerateNativeFunctionsIfNecessary(); - return this._nativeFunctions[functionName]; - } - }, { - key: 'GenerateNativeFunctionsIfNecessary', - value: function GenerateNativeFunctionsIfNecessary() { - if (this._nativeFunctions == null) { - this._nativeFunctions = {}; - - // Int operations - this.AddIntBinaryOp(this.Add, function (x, y) { - return x + y; - }); - this.AddIntBinaryOp(this.Subtract, function (x, y) { - return x - y; - }); - this.AddIntBinaryOp(this.Multiply, function (x, y) { - return x * y; - }); - this.AddIntBinaryOp(this.Divide, function (x, y) { - return parseInt(x / y); - }); - this.AddIntBinaryOp(this.Mod, function (x, y) { - return x % y; - }); - this.AddIntUnaryOp(this.Negate, function (x) { - return -x; - }); - - this.AddIntBinaryOp(this.Equal, function (x, y) { - return x == y ? 1 : 0; - }); - this.AddIntBinaryOp(this.Greater, function (x, y) { - return x > y ? 1 : 0; - }); - this.AddIntBinaryOp(this.Less, function (x, y) { - return x < y ? 1 : 0; - }); - this.AddIntBinaryOp(this.GreaterThanOrEquals, function (x, y) { - return x >= y ? 1 : 0; - }); - this.AddIntBinaryOp(this.LessThanOrEquals, function (x, y) { - return x <= y ? 1 : 0; - }); - this.AddIntBinaryOp(this.NotEquals, function (x, y) { - return x != y ? 1 : 0; - }); - this.AddIntUnaryOp(this.Not, function (x) { - return x == 0 ? 1 : 0; - }); - - this.AddIntBinaryOp(this.And, function (x, y) { - return x != 0 && y != 0 ? 1 : 0; - }); - this.AddIntBinaryOp(this.Or, function (x, y) { - return x != 0 || y != 0 ? 1 : 0; - }); - - this.AddIntBinaryOp(this.Max, function (x, y) { - return Math.max(x, y); - }); - this.AddIntBinaryOp(this.Min, function (x, y) { - return Math.min(x, y); - }); - - // Float operations - this.AddFloatBinaryOp(this.Add, function (x, y) { - return x + y; - }); - this.AddFloatBinaryOp(this.Subtract, function (x, y) { - return x - y; - }); - this.AddFloatBinaryOp(this.Multiply, function (x, y) { - return x * y; - }); - this.AddFloatBinaryOp(this.Divide, function (x, y) { - return x / y; - }); - this.AddFloatBinaryOp(this.Mod, function (x, y) { - return x % y; - }); // TODO: Is this the operation we want for floats? - this.AddFloatUnaryOp(this.Negate, function (x) { - return -x; - }); - - this.AddFloatBinaryOp(this.Equal, function (x, y) { - return x == y ? 1 : 0; - }); - this.AddFloatBinaryOp(this.Greater, function (x, y) { - return x > y ? 1 : 0; - }); - this.AddFloatBinaryOp(this.Less, function (x, y) { - return x < y ? 1 : 0; - }); - this.AddFloatBinaryOp(this.GreaterThanOrEquals, function (x, y) { - return x >= y ? 1 : 0; - }); - this.AddFloatBinaryOp(this.LessThanOrEquals, function (x, y) { - return x <= y ? 1 : 0; - }); - this.AddFloatBinaryOp(this.NotEquals, function (x, y) { - return x != y ? 1 : 0; - }); - this.AddFloatUnaryOp(this.Not, function (x) { - return x == 0.0 ? 1 : 0; - }); - - this.AddFloatBinaryOp(this.And, function (x, y) { - return x != 0.0 && y != 0.0 ? 1 : 0; - }); - this.AddFloatBinaryOp(this.Or, function (x, y) { - return x != 0.0 || y != 0.0 ? 1 : 0; - }); - - this.AddFloatBinaryOp(this.Max, function (x, y) { - return Math.max(x, y); - }); - this.AddFloatBinaryOp(this.Min, function (x, y) { - return Math.min(x, y); - }); - - // String operations - this.AddStringBinaryOp(this.Add, function (x, y) { - return x + y; - }); // concat - this.AddStringBinaryOp(this.Equal, function (x, y) { - return x === y ? 1 : 0; - }); - this.AddStringBinaryOp(this.NotEquals, function (x, y) { - return !(x === y) ? 1 : 0; - }); - - this.AddListBinaryOp(this.Add, function (x, y) { - return x.Union(y); - }); - this.AddListBinaryOp(this.Subtract, function (x, y) { - return x.Without(y); - }); - this.AddListBinaryOp(this.Has, function (x, y) { - return x.Contains(y) ? 1 : 0; - }); - this.AddListBinaryOp(this.Hasnt, function (x, y) { - return x.Contains(y) ? 0 : 1; - }); - this.AddListBinaryOp(this.Intersect, function (x, y) { - return x.Intersect(y); - }); - - this.AddListBinaryOp(this.Equal, function (x, y) { - return x.Equals(y) ? 1 : 0; - }); - this.AddListBinaryOp(this.Greater, function (x, y) { - return x.GreaterThan(y) ? 1 : 0; - }); - this.AddListBinaryOp(this.Less, function (x, y) { - return x.LessThan(y) ? 1 : 0; - }); - this.AddListBinaryOp(this.GreaterThanOrEquals, function (x, y) { - return x.GreaterThanOrEquals(y) ? 1 : 0; - }); - this.AddListBinaryOp(this.LessThanOrEquals, function (x, y) { - return x.LessThanOrEquals(y) ? 1 : 0; - }); - this.AddListBinaryOp(this.NotEquals, function (x, y) { - return !x.Equals(y) ? 1 : 0; - }); - - this.AddListBinaryOp(this.And, function (x, y) { - return x.Count > 0 && y.Count > 0 ? 1 : 0; - }); - this.AddListBinaryOp(this.Or, function (x, y) { - return x.Count > 0 || y.Count > 0 ? 1 : 0; - }); - - this.AddListUnaryOp(this.Not, function (x) { - return x.Count == 0 ? 1 : 0; - }); - - this.AddListUnaryOp(this.Invert, function (x) { - return x.inverse; - }); - this.AddListUnaryOp(this.All, function (x) { - return x.all; - }); - this.AddListUnaryOp(this.ListMin, function (x) { - return x.MinAsList(); - }); - this.AddListUnaryOp(this.ListMax, function (x) { - return x.MaxAsList(); - }); - this.AddListUnaryOp(this.Count, function (x) { - return x.Count; - }); - this.AddListUnaryOp(this.ValueOfList, function (x) { - return x.maxItem.Value; - }); - - // Special case: The only operation you can do on divert target values - var divertTargetsEqual = function divertTargetsEqual(d1, d2) { - return d1.Equals(d2) ? 1 : 0; - }; - this.AddOpToNativeFunc(this.Equal, 2, ValueType.DivertTarget, divertTargetsEqual); - } - } - }, { - key: 'AddOpToNativeFunc', - value: function AddOpToNativeFunc(name, args, valType, op) { - var nativeFunc = this._nativeFunctions[name]; - if (!nativeFunc) { - nativeFunc = NativeFunctionCall.internalConstructor(name, args); - this._nativeFunctions[name] = nativeFunc; - } - - nativeFunc.AddOpFuncForType(valType, op); - } - }, { - key: 'AddIntBinaryOp', - value: function AddIntBinaryOp(name, op) { - this.AddOpToNativeFunc(name, 2, ValueType.Int, op); - } - }, { - key: 'AddIntUnaryOp', - value: function AddIntUnaryOp(name, op) { - this.AddOpToNativeFunc(name, 1, ValueType.Int, op); - } - }, { - key: 'AddFloatBinaryOp', - value: function AddFloatBinaryOp(name, op) { - this.AddOpToNativeFunc(name, 2, ValueType.Float, op); - } - }, { - key: 'AddFloatUnaryOp', - value: function AddFloatUnaryOp(name, op) { - this.AddOpToNativeFunc(name, 1, ValueType.Float, op); - } - }, { - key: 'AddStringBinaryOp', - value: function AddStringBinaryOp(name, op) { - this.AddOpToNativeFunc(name, 2, ValueType.String, op); - } - }, { - key: 'AddListBinaryOp', - value: function AddListBinaryOp(name, op) { - this.AddOpToNativeFunc(name, 2, ValueType.List, op); - } - }, { - key: 'AddListUnaryOp', - value: function AddListUnaryOp(name, op) { - this.AddOpToNativeFunc(name, 1, ValueType.List, op); - } - }]); - return NativeFunctionCall; - }(Object$1); - - NativeFunctionCall.Add = "+"; - NativeFunctionCall.Subtract = "-"; - NativeFunctionCall.Divide = "/"; - NativeFunctionCall.Multiply = "*"; - NativeFunctionCall.Mod = "%"; - NativeFunctionCall.Negate = "_"; - - NativeFunctionCall.Equal = "=="; - NativeFunctionCall.Greater = ">"; - NativeFunctionCall.Less = "<"; - NativeFunctionCall.GreaterThanOrEquals = ">="; - NativeFunctionCall.LessThanOrEquals = "<="; - NativeFunctionCall.NotEquals = "!="; - NativeFunctionCall.Not = "!"; - - NativeFunctionCall.And = "&&"; - NativeFunctionCall.Or = "||"; - - NativeFunctionCall.Min = "MIN"; - NativeFunctionCall.Max = "MAX"; - - NativeFunctionCall.Has = "?"; - NativeFunctionCall.Hasnt = "!?"; - NativeFunctionCall.Intersect = "^"; - - NativeFunctionCall.ListMin = "LIST_MIN"; - NativeFunctionCall.ListMax = "LIST_MAX"; - NativeFunctionCall.All = "LIST_ALL"; - NativeFunctionCall.Count = "LIST_COUNT"; - NativeFunctionCall.ValueOfList = "LIST_VALUE"; - NativeFunctionCall.Invert = "LIST_INVERT"; - - NativeFunctionCall._nativeFunctions = null; - - var Tag = function (_InkObject) { - inherits(Tag, _InkObject); - - function Tag(tagText) { - classCallCheck(this, Tag); - - var _this = possibleConstructorReturn(this, (Tag.__proto__ || Object.getPrototypeOf(Tag)).call(this)); - - _this._text = tagText.toString() || ''; - return _this; - } - - createClass(Tag, [{ - key: 'toString', - value: function toString() { - return "# " + this._text; - } - }, { - key: 'text', - get: function get$$1() { - return this._text; - } - }]); - return Tag; - }(Object$1); - - var Choice = function () { - function Choice(choice) { - classCallCheck(this, Choice); - - this.text; - this.index; - this.choicePoint; - this.threadAtGeneration; - - this._originalThreadIndex; - this._originalChoicePath; - - if (choice) this.choicePoint = choice; - } - - createClass(Choice, [{ - key: "pathStringOnChoice", - get: function get$$1() { - return this.choicePoint.pathStringOnChoice; - } - }, { - key: "sourcePath", - get: function get$$1() { - return this.choicePoint.path.componentsString; - } - }]); - return Choice; - }(); - - var ListDefinition = function () { - function ListDefinition(name, items) { - classCallCheck(this, ListDefinition); - - this._name = name || ''; - this._items = null; - this._rawListItemsKeys = null; - this._itemNameToValues = items || {}; - } - - createClass(ListDefinition, [{ - key: 'forEachItems', - value: function forEachItems(fn) { - for (var key in this._rawListItemsKeys) { - fn({ - Key: this._rawListItemsKeys[key], - Value: this._items[key] - }); - } - } - }, { - key: 'ValueForItem', - value: function ValueForItem(item) { - var intVal = this._itemNameToValues[item.itemName]; - if (intVal !== undefined) return intVal;else return 0; - } - }, { - key: 'ContainsItem', - value: function ContainsItem(item) { - if (item.originName != this.name) return false; - - return item.itemName in this._itemNameToValues; - } - }, { - key: 'ContainsItemWithName', - value: function ContainsItemWithName(itemName) { - return this._itemNameToValues[itemName] !== undefined; - } - }, { - key: 'TryGetItemWithValue', - value: function TryGetItemWithValue(val, item) { - //item was an out - //the original function returns a boolean and has a second parameter called item that is an `out`. Both are needed and we can't just return the item because it'll always be truthy. Instead, we return an object containing the bool and the item - for (var key in this._itemNameToValues) { - if (this._itemNameToValues[key] == val) { - item = new InkListItem(this.name, key); - return { - item: item, - exists: true - }; - } - } - - item = InkListItem.Null; - return { - item: item, - exists: false - }; - } - }, { - key: 'TryGetValueForItem', - value: function TryGetValueForItem(item, intval) { - //intval is an out - intVal = this._itemNameToValues[item.itemName]; - return intVal; - } - }, { - key: 'ListRange', - value: function ListRange(min, max) { - var rawList = new InkList(); - for (var key in this._itemNameToValues) { - if (this._itemNameToValues[key] >= min && this._itemNameToValues[key] <= max) { - var item = new InkListItem(this.name, key); - rawList.Add(item, this._itemNameToValues[key]); - } - } - return new ListValue(rawList); - } - }, { - key: 'name', - get: function get$$1() { - return this._name; - } - }, { - key: 'items', - get: function get$$1() { - if (this._items == null) { - this._items = {}; - this._rawListItemsKeys = {}; - for (var key in this._itemNameToValues) { - var item = new InkListItem(this.name, key); - this._rawListItemsKeys[item] = item; - this._items[item] = this._itemNameToValues[key]; - } - } - this._items.forEach = this.forEachItems.bind(this); - - return this._items; - } - }]); - return ListDefinition; - }(); - - var ListDefinitionsOrigin = function () { - function ListDefinitionsOrigin(lists) { - var _this = this; - - classCallCheck(this, ListDefinitionsOrigin); - - this._lists = {}; - - lists.forEach(function (list) { - _this._lists[list.name] = list; - }); - } - - createClass(ListDefinitionsOrigin, [{ - key: 'TryGetDefinition', - value: function TryGetDefinition(name, def) { - //initially, this function returns a boolean and the second parameter is an out. - return name in this._lists ? this._lists[name] : def; - } - }, { - key: 'FindSingleItemListWithName', - value: function FindSingleItemListWithName(name) { - var item = InkListItem.Null; - var list = null; - - var nameParts = name.split('.'); - if (nameParts.length == 2) { - item = new InkListItem(nameParts[0], nameParts[1]); - list = this.TryGetDefinition(item.originName, list); - } else { - for (var key in this._lists) { - var listWithItem = this._lists[key]; - item = new InkListItem(key, name); - if (listWithItem.ContainsItem(item)) { - list = listWithItem; - break; - } - } - } - - if (list != null) { - var itemValue = list.ValueForItem(item); - return new ListValue(item, itemValue); - } - - return null; - } - }, { - key: 'lists', - get: function get$$1() { - var listOfLists = []; - - for (var key in this._lists) { - listOfLists.push(this._lists[key]); - } - return listOfLists; - } - }]); - return ListDefinitionsOrigin; - }(); - - var JsonSerialisation = function () { - function JsonSerialisation() { - classCallCheck(this, JsonSerialisation); - } - - createClass(JsonSerialisation, null, [{ - key: 'ListToJArray', - value: function ListToJArray(serialisables) { - var _this = this; - - var jArray = []; - serialisables.forEach(function (s) { - jArray.push(_this.RuntimeObjectToJToken(s)); - }); - return jArray; - } - }, { - key: 'JArrayToRuntimeObjList', - value: function JArrayToRuntimeObjList(jArray, skipLast) { - var count = jArray.length; - if (skipLast) count--; - - var list = []; - - for (var i = 0; i < count; i++) { - var jTok = jArray[i]; - var runtimeObj = this.JTokenToRuntimeObject(jTok); - list.push(runtimeObj); - } - - return list; - } - }, { - key: 'JObjectToDictionaryRuntimeObjs', - value: function JObjectToDictionaryRuntimeObjs(jObject) { - var dict = {}; - - for (var key in jObject) { - dict[key] = this.JTokenToRuntimeObject(jObject[key]); - } - - return dict; - } - }, { - key: 'DictionaryRuntimeObjsToJObject', - value: function DictionaryRuntimeObjsToJObject(dictionary) { - var jsonObj = {}; - - for (var key in dictionary) { - // var runtimeObj = keyVal.Value as Runtime.Object; - var runtimeObj = dictionary[key]; - if (runtimeObj instanceof Object$1) jsonObj[key] = this.RuntimeObjectToJToken(runtimeObj); - } - - return jsonObj; - } - }, { - key: 'JObjectToIntDictionary', - value: function JObjectToIntDictionary(jObject) { - var dict = {}; - for (var key in jObject) { - dict[key] = parseInt(jObject[key]); - } - return dict; - } - }, { - key: 'IntDictionaryToJObject', - value: function IntDictionaryToJObject(dict) { - var jObj = {}; - for (var key in dict) { - jObj[key] = dict[key]; - } - return jObj; - } - }, { - key: 'JTokenToRuntimeObject', - value: function JTokenToRuntimeObject(token) { - //@TODO probably find a more robust way to detect numbers, isNaN seems happy to accept things that really aren't numberish. - if (!isNaN(token) && token !== "\n") { - //JS thinks "\n" is a number - return Value.Create(token); - } - - if (typeof token === 'string') { - var str = token.toString(); - - // String value - var firstChar = str[0]; - if (firstChar == '^') return new StringValue(str.substring(1));else if (firstChar == "\n" && str.length == 1) return new StringValue("\n"); - - // Glue - if (str == "<>") return new Glue(GlueType.Bidirectional);else if (str == "G<") return new Glue(GlueType.Left);else if (str == "G>") return new Glue(GlueType.Right); - - // Control commands (would looking up in a hash set be faster?) - for (var i = 0; i < _controlCommandNames.length; ++i) { - var cmdName = _controlCommandNames[i]; - if (str == cmdName) { - return new ControlCommand(i); - } - } - - // Native functions - if (str == "L^") str = "^"; - if (NativeFunctionCall.CallExistsWithName(str)) return NativeFunctionCall.CallWithName(str); - - // Pop - if (str == "->->") return ControlCommand.PopTunnel();else if (str == "~ret") return ControlCommand.PopFunction(); - - // Void - if (str == "void") return new Void(); - } - - if ((typeof token === 'undefined' ? 'undefined' : _typeof(token)) === 'object' && token instanceof Array === false) { - var obj = token; - var propValue; - - // Divert target value to path - if (obj["^->"]) { - propValue = obj["^->"]; - return new DivertTargetValue(new Path$1(propValue.toString())); - } - - // VariablePointerValue - if (obj["^var"]) { - propValue = obj["^var"]; - var varPtr = new VariablePointerValue(propValue.toString()); - if (obj["ci"]) { - propValue = obj["ci"]; - varPtr.contextIndex = parseInt(propValue); - } - return varPtr; - } - - // Divert - var isDivert = false; - var pushesToStack = false; - var divPushType = PushPopType.Function; - var external = false; - if (propValue = obj["->"]) { - isDivert = true; - } else if (propValue = obj["f()"]) { - isDivert = true; - pushesToStack = true; - divPushType = PushPopType.Function; - } else if (propValue = obj["->t->"]) { - isDivert = true; - pushesToStack = true; - divPushType = PushPopType.Tunnel; - } else if (propValue = obj["x()"]) { - isDivert = true; - external = true; - pushesToStack = false; - divPushType = PushPopType.Function; - } - - if (isDivert) { - var divert = new Divert(); - divert.pushesToStack = pushesToStack; - divert.stackPushType = divPushType; - divert.isExternal = external; - - var target = propValue.toString(); - - if (propValue = obj["var"]) divert.variableDivertName = target;else divert.targetPathString = target; - - divert.isConditional = !!obj["c"]; - - if (external) { - if (propValue = obj["exArgs"]) divert.externalArgs = parseInt(propValue); - } - - return divert; - } - - // Choice - if (propValue = obj["*"]) { - var choice = new ChoicePoint(); - choice.pathStringOnChoice = propValue.toString(); - - if (propValue = obj["flg"]) choice.flags = parseInt(propValue); - - return choice; - } - - // Variable reference - if (propValue = obj["VAR?"]) { - return new VariableReference(propValue.toString()); - } else if (propValue = obj["CNT?"]) { - var readCountVarRef = new VariableReference(); - readCountVarRef.pathStringForCount = propValue.toString(); - return readCountVarRef; - } - - // Variable assignment - var isVarAss = false; - var isGlobalVar = false; - if (propValue = obj["VAR="]) { - isVarAss = true; - isGlobalVar = true; - } else if (propValue = obj["temp="]) { - isVarAss = true; - isGlobalVar = false; - } - if (isVarAss) { - var varName = propValue.toString(); - var isNewDecl = !obj["re"]; - var varAss = new VariableAssignment(varName, isNewDecl); - varAss.isGlobal = isGlobalVar; - return varAss; - } - if (obj["#"] !== undefined) { - propValue = obj["#"]; - return new Tag(propValue.toString()); - } - //list value - if (propValue = obj["list"]) { - // var listContent = (Dictionary)propValue; - var listContent = propValue; - var rawList = new InkList(); - if (propValue = obj["origins"]) { - // var namesAsObjs = (List)propValue; - var namesAsObjs = propValue; - // rawList.SetInitialOriginNames(namesAsObjs.Cast().ToList()); - rawList.SetInitialOriginNames(namesAsObjs); - } - - for (var key in listContent) { - var nameToVal = listContent[key]; - var item = new InkListItem(key); - var val = parseInt(nameToVal); - rawList.Add(item, val); - } - - return new ListValue(rawList); - } - - if (obj["originalChoicePath"] != null) return this.JObjectToChoice(obj); - } - - // Array is always a Runtime.Container - if (token instanceof Array) { - return this.JArrayToContainer(token); - } - - if (token == null) return null; - - throw "Failed to convert token to runtime object: " + JSON.stringify(token); - } - }, { - key: 'RuntimeObjectToJToken', - value: function RuntimeObjectToJToken(obj) { - // var container = obj as Container; - var container = obj; - if (container instanceof Container) { - return this.ContainerToJArray(container); - } - - // var divert = obj as Divert; - var divert = obj; - if (divert instanceof Divert) { - var divTypeKey = "->"; - if (divert.isExternal) divTypeKey = "x()";else if (divert.pushesToStack) { - if (divert.stackPushType == PushPopType.Function) divTypeKey = "f()";else if (divert.stackPushType == PushPopType.Tunnel) divTypeKey = "->t->"; - } - - var targetStr; - if (divert.hasVariableTarget) targetStr = divert.variableDivertName;else targetStr = divert.targetPathString; - - var jObj = {}; - jObj[divTypeKey] = targetStr; - - if (divert.hasVariableTarget) jObj["var"] = true; - - if (divert.isConditional) jObj["c"] = true; - - if (divert.externalArgs > 0) jObj["exArgs"] = divert.externalArgs; - - return jObj; - } - - // var choicePoint = obj as ChoicePoint; - var choicePoint = obj; - if (choicePoint instanceof ChoicePoint) { - var jObj = {}; - jObj["*"] = choicePoint.pathStringOnChoice; - jObj["flg"] = choicePoint.flags; - return jObj; - } - - // var intVal = obj as IntValue; - var intVal = obj; - if (intVal instanceof IntValue) return intVal.value; - - // var floatVal = obj as FloatValue; - var floatVal = obj; - if (floatVal instanceof FloatValue) return floatVal.value; - - // var strVal = obj as StringValue; - var strVal = obj; - if (strVal instanceof StringValue) { - if (strVal.isNewline) return "\n";else return "^" + strVal.value; - } - - // var listVal = obj as ListValue; - var listVal = obj; - if (listVal instanceof ListValue) { - return this.InkListToJObject(listVal); - } - - // var divTargetVal = obj as DivertTargetValue; - var divTargetVal = obj; - if (divTargetVal instanceof DivertTargetValue) return { - "^->": divTargetVal.value.componentsString - }; - - // var varPtrVal = obj as VariablePointerValue; - var varPtrVal = obj; - if (varPtrVal instanceof VariablePointerValue) return { - "^var": varPtrVal.value, - "ci": varPtrVal.contextIndex - }; - - // var glue = obj as Runtime.Glue; - var glue = obj; - if (glue instanceof Glue) { - if (glue.isBi) return "<>";else if (glue.isLeft) return "G<";else return "G>"; - } - - // var controlCmd = obj as ControlCommand; - var controlCmd = obj; - if (controlCmd instanceof ControlCommand) { - return _controlCommandNames[parseInt(controlCmd.commandType)]; - } - - // var nativeFunc = obj as Runtime.NativeFunctionCall; - var nativeFunc = obj; - if (nativeFunc instanceof NativeFunctionCall) { - var name = nativeFunc.name; - - // Avoid collision with ^ used to indicate a string - if (name == "^") name = "L^"; - return name; - } - - // Variable reference - // var varRef = obj as VariableReference; - var varRef = obj; - if (varRef instanceof VariableReference) { - var jObj = {}; - var readCountPath = varRef.pathStringForCount; - if (readCountPath != null) { - jObj["CNT?"] = readCountPath; - } else { - jObj["VAR?"] = varRef.name; - } - - return jObj; - } - - // Variable assignment - // var varAss = obj as VariableAssignment; - var varAss = obj; - if (varAss instanceof VariableAssignment) { - var key = varAss.isGlobal ? "VAR=" : "temp="; - var jObj = {}; - jObj[key] = varAss.variableName; - - // Reassignment? - if (!varAss.isNewDeclaration) jObj["re"] = true; - - return jObj; - } - - // var voidObj = obj as Void; - var voidObj = obj; - if (voidObj instanceof Void) return "void"; - - // var tag = obj as Tag; - var tag = obj; - if (tag instanceof Tag) { - var jObj = {}; - jObj["#"] = tag.text; - return jObj; - } - - // Used when serialising save state only - // var choice = obj as Choice; - var choice = obj; - if (choice instanceof Choice) return this.ChoiceToJObject(choice); - - throw "Failed to convert runtime object to Json token: " + obj; - } - }, { - key: 'ContainerToJArray', - value: function ContainerToJArray(container) { - var jArray = this.ListToJArray(container.content); - - // Container is always an array [...] - // But the final element is always either: - // - a dictionary containing the named content, as well as possibly - // the key "#" with the count flags - // - null, if neither of the above - var namedOnlyContent = container.namedOnlyContent; - var countFlags = container.countFlags; - if (namedOnlyContent != null && namedOnlyContent.length > 0 || countFlags > 0 || container.name != null) { - - var terminatingObj; - if (namedOnlyContent != null) { - terminatingObj = this.DictionaryRuntimeObjsToJObject(namedOnlyContent); - - // Strip redundant names from containers if necessary - for (var key in terminatingObj) { - // var subContainerJArray = namedContentObj.Value as JArray; - var subContainerJArray = terminatingObj[key]; - if (subContainerJArray != null) { - // var attrJObj = subContainerJArray [subContainerJArray.Count - 1] as JObject; - var attrJObj = subContainerJArray[subContainerJArray.length - 1]; - if (attrJObj != null) { - delete attrJObj["#n"]; - if (Object.keys(attrJObj).length == 0) subContainerJArray[subContainerJArray.length - 1] = null; - } - } - } - } else terminatingObj = {}; - - if (countFlags > 0) terminatingObj["#f"] = countFlags; - - if (container.name != null) terminatingObj["#n"] = container.name; - - jArray.push(terminatingObj); - } - - // Add null terminator to indicate that there's no dictionary - else { - jArray.push(null); - } - - return jArray; - } - }, { - key: 'JArrayToContainer', - value: function JArrayToContainer(jArray) { - var container = new Container(); - container.content = this.JArrayToRuntimeObjList(jArray, true); - - // Final object in the array is always a combination of - // - named content - // - a "#" key with the countFlags - // (if either exists at all, otherwise null) - // var terminatingObj = jArray [jArray.Count - 1] as JObject; - var terminatingObj = jArray[jArray.length - 1]; - if (terminatingObj != null) { - - var namedOnlyContent = {}; - - for (var key in terminatingObj) { - if (key == "#f") { - container.countFlags = parseInt(terminatingObj[key]); - } else if (key == "#n") { - container.name = terminatingObj[key].toString(); - } else { - var namedContentItem = this.JTokenToRuntimeObject(terminatingObj[key]); - // var namedSubContainer = namedContentItem as Container; - var namedSubContainer = namedContentItem; - if (namedSubContainer instanceof Container) namedSubContainer.name = key; - namedOnlyContent[key] = namedContentItem; - } - } - - container.namedOnlyContent = namedOnlyContent; - } - - return container; - } - }, { - key: 'JObjectToChoice', - value: function JObjectToChoice(jObj) { - var choice = new Choice(); - choice.text = jObj["text"].toString(); - choice.index = parseInt(jObj["index"]); - choice.originalChoicePath = jObj["originalChoicePath"].toString(); - choice.originalThreadIndex = parseInt(jObj["originalThreadIndex"]); - return choice; - } - }, { - key: 'ChoiceToJObject', - value: function ChoiceToJObject(choice) { - var jObj = {}; - jObj["text"] = choice.text; - jObj["index"] = choice.index; - jObj["originalChoicePath"] = choice.originalChoicePath; - jObj["originalThreadIndex"] = choice.originalThreadIndex; - return jObj; - } - }, { - key: 'InkListToJObject', - value: function InkListToJObject(listVal) { - var rawList = listVal.value; - - var dict = {}; - - var content = {}; - - rawList.forEach(function (itemAndValue) { - var item = itemAndValue.Key; - var val = itemAndValue.Value; - content[item.toString()] = val; - }); - - dict["list"] = content; - - if (rawList.Count == 0 && rawList.originNames != null && rawList.originNames.length > 0) { - // dict["origins"] = rawList.originNames.Cast ().ToList (); - dict["origins"] = rawList.originNames; - } - - return dict; - } - }, { - key: 'ListDefinitionsToJToken', - value: function ListDefinitionsToJToken(origin) { - var result = {}; - - origin.lists.forEach(function (def) { - var listDefJson = {}; - def.items.forEach(function (itemToVal) { - var item = itemToVal.Key; - var val = itemToVal.Value; - listDefJson[item.itemName] = val; - }); - - result[def.name] = listDefJson; - }); - - return result; - } - }, { - key: 'JTokenToListDefinitions', - value: function JTokenToListDefinitions(obj) { - // var defsObj = (Dictionary)obj; - var defsObj = obj; - - var allDefs = []; - - for (var key in defsObj) { - var name = key.toString(); - // var listDefJson = (Dictionary)kv.Value; - var listDefJson = defsObj[key]; - - // Cast (string, object) to (string, int) for items - var items = {}; - - for (var nameValueKey in listDefJson) { - var nameValue = listDefJson[nameValueKey]; - items[nameValueKey] = parseInt(nameValue); - } - - var def = new ListDefinition(name, items); - allDefs.push(def); - } - - return new ListDefinitionsOrigin(allDefs); - } - }]); - return JsonSerialisation; - }(); - - var _controlCommandNames = []; - - _controlCommandNames[ControlCommand.CommandType.EvalStart] = "ev"; - _controlCommandNames[ControlCommand.CommandType.EvalOutput] = "out"; - _controlCommandNames[ControlCommand.CommandType.EvalEnd] = "/ev"; - _controlCommandNames[ControlCommand.CommandType.Duplicate] = "du"; - _controlCommandNames[ControlCommand.CommandType.PopEvaluatedValue] = "pop"; - _controlCommandNames[ControlCommand.CommandType.PopFunction] = "~ret"; - _controlCommandNames[ControlCommand.CommandType.PopTunnel] = "->->"; - _controlCommandNames[ControlCommand.CommandType.BeginString] = "str"; - _controlCommandNames[ControlCommand.CommandType.EndString] = "/str"; - _controlCommandNames[ControlCommand.CommandType.NoOp] = "nop"; - _controlCommandNames[ControlCommand.CommandType.ChoiceCount] = "choiceCnt"; - _controlCommandNames[ControlCommand.CommandType.TurnsSince] = "turns"; - _controlCommandNames[ControlCommand.CommandType.ReadCount] = "readc"; - _controlCommandNames[ControlCommand.CommandType.Random] = "rnd"; - _controlCommandNames[ControlCommand.CommandType.SeedRandom] = "srnd"; - _controlCommandNames[ControlCommand.CommandType.VisitIndex] = "visit"; - _controlCommandNames[ControlCommand.CommandType.SequenceShuffleIndex] = "seq"; - _controlCommandNames[ControlCommand.CommandType.StartThread] = "thread"; - _controlCommandNames[ControlCommand.CommandType.Done] = "done"; - _controlCommandNames[ControlCommand.CommandType.End] = "end"; - _controlCommandNames[ControlCommand.CommandType.ListFromInt] = "listInt"; - _controlCommandNames[ControlCommand.CommandType.ListRange] = "range"; - - for (var i$1 = 0; i$1 < ControlCommand.CommandType.TOTAL_VALUES; ++i$1) { - if (_controlCommandNames[i$1] == null) throw "Control command not accounted for in serialisation"; - } - - var Element = function () { - function Element(type, container, contentIndex, inExpressionEvaluation) { - classCallCheck(this, Element); - - this.currentContainer = container; - this.currentContentIndex = contentIndex; - this.inExpressionEvaluation = inExpressionEvaluation || false; - this.temporaryVariables = {}; - this.type = type; - } - - createClass(Element, [{ - key: 'Copy', - value: function Copy() { - var copy = new Element(this.type, this.currentContainer, this.currentContentIndex, this.inExpressionEvaluation); - _extends(copy.temporaryVariables, this.temporaryVariables); - return copy; - } - }, { - key: 'currentObject', - get: function get$$1() { - if (this.currentContainer && this.currentContentIndex < this.currentContainer.content.length) { - return this.currentContainer.content[this.currentContentIndex]; - } - - return null; - }, - set: function set$$1(value) { - var currentObj = value; - if (currentObj == null) { - this.currentContainer = null; - this.currentContentIndex = 0; - return; - } - - // currentContainer = currentObj.parent as Container; - this.currentContainer = currentObj.parent; - if (this.currentContainer instanceof Container) this.currentContentIndex = this.currentContainer.content.indexOf(currentObj); - - // Two reasons why the above operation might not work: - // - currentObj is already the root container - // - currentObj is a named container rather than being an object at an index - if (this.currentContainer instanceof Container === false || this.currentContentIndex == -1) { - // currentContainer = currentObj as Container; - this.currentContainer = currentObj; - this.currentContentIndex = 0; - } - } - }]); - return Element; - }(); - - var Thread = function () { - function Thread(jsonToken, storyContext) { - var _this = this; - - classCallCheck(this, Thread); - - this.callstack = []; - this.threadIndex = 0; - this.previousContentObject = null; - - if (jsonToken && storyContext) { - var jThreadObj = jsonToken; - this.threadIndex = parseInt(jThreadObj["threadIndex"]); - - var jThreadCallstack = jThreadObj["callstack"]; - - jThreadCallstack.forEach(function (jElTok) { - var jElementObj = jElTok; - - var pushPopType = parseInt(jElementObj["type"]); - - var currentContainer = null; - var contentIndex = 0; - - var currentContainerPathStr = null; - var currentContainerPathStrToken = jElementObj["cPath"]; - if (typeof currentContainerPathStrToken !== 'undefined') { - currentContainerPathStr = currentContainerPathStrToken.toString(); - // currentContainer = storyContext.ContentAtPath (new Path(currentContainerPathStr)) as Container; - currentContainer = storyContext.ContentAtPath(new Path$1(currentContainerPathStr)); - contentIndex = parseInt(jElementObj["idx"]); - } - - var inExpressionEvaluation = !!jElementObj["exp"]; - - var el = new Element(pushPopType, currentContainer, contentIndex, inExpressionEvaluation); - - var jObjTemps = jElementObj["temp"]; - el.temporaryVariables = JsonSerialisation.JObjectToDictionaryRuntimeObjs(jObjTemps); - - _this.callstack.push(el); - }); - - var prevContentObjPath = jThreadObj["previousContentObject"]; - if (typeof prevContentObjPath !== 'undefined') { - var prevPath = new Path$1(prevContentObjPath.toString()); - this.previousContentObject = storyContext.ContentAtPath(prevPath); - } - } - } - - createClass(Thread, [{ - key: 'Copy', - value: function Copy() { - var copy = new Thread(); - copy.threadIndex = this.threadIndex; - this.callstack.forEach(function (e) { - copy.callstack.push(e.Copy()); - }); - copy.previousContentObject = this.previousContentObject; - return copy; - } - }, { - key: 'jsonToken', - get: function get$$1() { - var threadJObj = {}; - - var jThreadCallstack = []; - this.callstack.forEach(function (el) { - var jObj = {}; - if (el.currentContainer) { - jObj["cPath"] = el.currentContainer.path.componentsString; - jObj["idx"] = el.currentContentIndex; - } - jObj["exp"] = el.inExpressionEvaluation; - jObj["type"] = parseInt(el.type); - jObj["temp"] = JsonSerialisation.DictionaryRuntimeObjsToJObject(el.temporaryVariables); - jThreadCallstack.push(jObj); - }); - - threadJObj["callstack"] = jThreadCallstack; - threadJObj["threadIndex"] = this.threadIndex; - - if (this.previousContentObject != null) threadJObj["previousContentObject"] = this.previousContentObject.path.toString(); - - return threadJObj; - } - }]); - return Thread; - }(); - - var CallStack = function () { - function CallStack(copyOrrootContentContainer) { - var _this2 = this; - - classCallCheck(this, CallStack); - - this._threads = []; - this._threadCounter = 0; - this._threads.push(new Thread()); - - if (copyOrrootContentContainer instanceof CallStack) { - this._threads = []; - - copyOrrootContentContainer._threads.forEach(function (otherThread) { - _this2._threads.push(otherThread.Copy()); - }); - } else { - this._threads[0].callstack.push(new Element(PushPopType.Tunnel, copyOrrootContentContainer, 0)); - } - } - - createClass(CallStack, [{ - key: 'CanPop', - value: function CanPop(type) { - if (!this.canPop) return false; - - if (type == null) return true; - - return this.currentElement.type == type; - } - }, { - key: 'Pop', - value: function Pop(type) { - if (this.CanPop(type)) { - this.callStack.pop(); - return; - } else { - throw "Mismatched push/pop in Callstack"; - } - } - }, { - key: 'Push', - value: function Push(type) { - // When pushing to callstack, maintain the current content path, but jump out of expressions by default - this.callStack.push(new Element(type, this.currentElement.currentContainer, this.currentElement.currentContentIndex, false)); - } - }, { - key: 'PushThread', - value: function PushThread() { - var newThread = this.currentThread.Copy(); - this._threadCounter++; - newThread.threadIndex = this._threadCounter; - this._threads.push(newThread); - } - }, { - key: 'PopThread', - value: function PopThread() { - if (this.canPopThread) { - this._threads.splice(this._threads.indexOf(this.currentThread), 1); //should be equivalent to a pop() - } else { - throw "Can't pop thread"; - } - } - }, { - key: 'SetJsonToken', - value: function SetJsonToken(token, storyContext) { - var _this3 = this; - - this._threads.length = 0; - - var jObject = token; - - var jThreads = jObject["threads"]; - - jThreads.forEach(function (jThreadTok) { - var thread = new Thread(jThreadTok, storyContext); - _this3._threads.push(thread); - }); - - this._threadCounter = parseInt(jObject["threadCounter"]); - } - }, { - key: 'GetJsonToken', - value: function GetJsonToken() { - var jObject = {}; - - var jThreads = []; - this._threads.forEach(function (thread) { - jThreads.push(thread.jsonToken); - }); - - jObject["threads"] = jThreads; - jObject["threadCounter"] = this._threadCounter; - - return jObject; - } - }, { - key: 'GetTemporaryVariableWithName', - value: function GetTemporaryVariableWithName(name, contextIndex) { - contextIndex = typeof contextIndex === 'undefined' ? -1 : contextIndex; - - if (contextIndex == -1) contextIndex = this.currentElementIndex + 1; - - var varValue = null; - - var contextElement = this.callStack[contextIndex - 1]; - - if (varValue = contextElement.temporaryVariables[name]) { - return varValue; - } else { - return null; - } - } - }, { - key: 'SetTemporaryVariable', - value: function SetTemporaryVariable(name, value, declareNew, contextIndex) { - contextIndex = typeof contextIndex === 'undefined' ? -1 : contextIndex; - - if (contextIndex == -1) contextIndex = this.currentElementIndex + 1; - - var contextElement = this.callStack[contextIndex - 1]; - - if (!declareNew && !contextElement.temporaryVariables[name]) { - throw new StoryException("Could not find temporary variable to set: " + name); - } - - var oldValue; - if (oldValue = contextElement.temporaryVariables[name]) ListValue.RetainListOriginsForAssignment(oldValue, value); - - contextElement.temporaryVariables[name] = value; - } - }, { - key: 'ContextForVariableNamed', - value: function ContextForVariableNamed(name) { - // Current temporary context? - // (Shouldn't attempt to access contexts higher in the callstack.) - if (this.currentElement.temporaryVariables[name]) { - return this.currentElementIndex + 1; - } - - // Global - else { - return 0; - } - } - }, { - key: 'ThreadWithIndex', - value: function ThreadWithIndex(index) { - var filtered = this._threads.filter(function (t) { - if (t.threadIndex == index) return t; - }); - - return filtered[0]; - } - }, { - key: 'currentThread', - get: function get$$1() { - return this._threads[this._threads.length - 1]; - }, - set: function set$$1(value) { - if (this._threads.length != 1) console.warn("Shouldn't be directly setting the current thread when we have a stack of them"); - - this._threads.length = 0; - this._threads.push(value); - } - }, { - key: 'callStack', - get: function get$$1() { - return this.currentThread.callstack; - } - }, { - key: 'elements', - get: function get$$1() { - return this.callStack; - } - }, { - key: 'depth', - get: function get$$1() { - return this.elements.length; - } - }, { - key: 'currentElement', - get: function get$$1() { - return this.callStack[this.callStack.length - 1]; - } - }, { - key: 'currentElementIndex', - get: function get$$1() { - return this.callStack.length - 1; - } - }, { - key: 'canPop', - get: function get$$1() { - return this.callStack.length > 1; - } - }, { - key: 'canPopThread', - get: function get$$1() { - return this._threads.length > 1; - } - }]); - return CallStack; - }(); - - //still needs: - // - varchanged events - // - see if the internal getenumarators are needed - var VariablesState = function () { - function VariablesState(callStack, listDefsOrigin) { - classCallCheck(this, VariablesState); - - this._globalVariables = {}; - this._callStack = callStack; - this._listDefsOrigin = listDefsOrigin; - - this._batchObservingVariableChanges = null; - this._changedVariables = null; - - //the way variableChangedEvent is a bit different than the reference implementation. Originally it uses the C# += operator to add delegates, but in js we need to maintain an actual collection of delegates (ie. callbacks) - //to register a new one, there is a special ObserveVariableChange method below. - this.variableChangedEvent = null; - this.variableChangedEventCallbacks = []; - - //if es6 proxies are available, use them. - try { - //the proxy is used to allow direct manipulation of global variables. It first tries to access the objetcs own property, and if none is found it delegates the call to the $ method, defined below - var p = new Proxy(this, { - get: function get$$1(target, name) { - return name in target ? target[name] : target.$(name); - }, - set: function set$$1(target, name, value) { - if (name in target) target[name] = value;else target.$(name, value); - return true; //returning a fasly value make sthe trap fail - } - }); - - return p; - } catch (e) { - //thr proxy object is not available in this context. we should warn the dev but writting to the console feels a bit intrusive. - // console.log("ES6 Proxy not available - direct manipulation of global variables can't work, use $() instead."); - } - } - - createClass(VariablesState, [{ - key: 'ObserveVariableChange', - - - /** - * This function is specific to the js version of ink. It allows to register a callback that will be called when a variable changes. The original code uses `state.variableChangedEvent += callback` instead. - * @param {function} callback - */ - value: function ObserveVariableChange(callback) { - var _this = this; - - if (this.variableChangedEvent == null) { - this.variableChangedEvent = function (variableName, newValue) { - _this.variableChangedEventCallbacks.forEach(function (cb) { - cb(variableName, newValue); - }); - }; - } - - this.variableChangedEventCallbacks.push(callback); - } - }, { - key: 'CopyFrom', - value: function CopyFrom(toCopy) { - this._globalVariables = _extends({}, toCopy._globalVariables); - - this.variableChangedEvent = toCopy.variableChangedEvent; - - if (toCopy.batchObservingVariableChanges != this.batchObservingVariableChanges) { - - if (toCopy.batchObservingVariableChanges) { - this._batchObservingVariableChanges = true; - this._changedVariables = toCopy._changedVariables; - } else { - this._batchObservingVariableChanges = false; - this._changedVariables = null; - } - } - } - }, { - key: 'GetVariableWithName', - value: function GetVariableWithName(name, contextIndex) { - if (typeof contextIndex === 'undefined') contextIndex = -1; - - var varValue = this.GetRawVariableWithName(name, contextIndex); - - // Get value from pointer? - // var varPointer = varValue as VariablePointerValue; - var varPointer = varValue; - if (varPointer instanceof VariablePointerValue) { - varValue = this.ValueAtVariablePointer(varPointer); - } - - return varValue; - } - }, { - key: 'GetRawVariableWithName', - value: function GetRawVariableWithName(name, contextIndex) { - var varValue = null; - - // 0 context = global - if (contextIndex == 0 || contextIndex == -1) { - if (varValue = this._globalVariables[name]) return varValue; - - var listItemValue = this._listDefsOrigin.FindSingleItemListWithName(name); - if (listItemValue) return listItemValue; - } - - // Temporary - varValue = this._callStack.GetTemporaryVariableWithName(name, contextIndex); - - if (varValue == null) throw "RUNTIME ERROR: Variable '" + name + "' could not be found in context '" + contextIndex + "'. This shouldn't be possible so is a bug in the ink engine. Please try to construct a minimal story that reproduces the problem and report to inkle, thank you!"; - - return varValue; - } - }, { - key: 'ValueAtVariablePointer', - value: function ValueAtVariablePointer(pointer) { - return this.GetVariableWithName(pointer.variableName, pointer.contextIndex); - } - }, { - key: 'Assign', - value: function Assign(varAss, value) { - var name = varAss.variableName; - var contextIndex = -1; - - // Are we assigning to a global variable? - var setGlobal = false; - if (varAss.isNewDeclaration) { - setGlobal = varAss.isGlobal; - } else { - setGlobal = !!this._globalVariables[name]; - } - - // Constructing new variable pointer reference - if (varAss.isNewDeclaration) { - // var varPointer = value as VariablePointerValue; - var varPointer = value; - if (varPointer instanceof VariablePointerValue) { - var fullyResolvedVariablePointer = this.ResolveVariablePointer(varPointer); - value = fullyResolvedVariablePointer; - } - } - - // Assign to existing variable pointer? - // Then assign to the variable that the pointer is pointing to by name. - else { - - // De-reference variable reference to point to - var existingPointer = null; - do { - // existingPointer = GetRawVariableWithName (name, contextIndex) as VariablePointerValue; - existingPointer = this.GetRawVariableWithName(name, contextIndex); - if (existingPointer instanceof VariablePointerValue) { - name = existingPointer.variableName; - contextIndex = existingPointer.contextIndex; - setGlobal = contextIndex == 0; - } - } while (existingPointer instanceof VariablePointerValue); - } - - if (setGlobal) { - this.SetGlobal(name, value); - } else { - this._callStack.SetTemporaryVariable(name, value, varAss.isNewDeclaration, contextIndex); - } - } - }, { - key: 'RetainListOriginsForAssignment', - value: function RetainListOriginsForAssignment(oldValue, newValue) { - // var oldList = oldValue as ListValue; - var oldList = oldValue; - // var newList = newValue as ListValue; - var newList = newValue; - - if (oldList instanceof ListValue && newList instanceof ListValue && newList.value.Count == 0) newList.value.SetInitialOriginNames(oldList.value.originNames); - } - }, { - key: 'SetGlobal', - value: function SetGlobal(variableName, value) { - var oldValue = null; - oldValue = this._globalVariables[variableName]; - - ListValue.RetainListOriginsForAssignment(oldValue, value); - - this._globalVariables[variableName] = value; - - if (this.variableChangedEvent != null && value !== oldValue) { - - if (this.batchObservingVariableChanges) { - this._changedVariables.push(variableName); - } else { - this.variableChangedEvent(variableName, value); - } - } - } - }, { - key: 'ResolveVariablePointer', - value: function ResolveVariablePointer(varPointer) { - var contextIndex = varPointer.contextIndex; - - if (contextIndex == -1) contextIndex = this.GetContextIndexOfVariableNamed(varPointer.variableName); - - var valueOfVariablePointedTo = this.GetRawVariableWithName(varPointer.variableName, contextIndex); - - // Extra layer of indirection: - // When accessing a pointer to a pointer (e.g. when calling nested or - // recursive functions that take a variable references, ensure we don't create - // a chain of indirection by just returning the final target. - // var doubleRedirectionPointer = valueOfVariablePointedTo as VariablePointerValue; - var doubleRedirectionPointer = valueOfVariablePointedTo; - if (doubleRedirectionPointer instanceof VariablePointerValue) { - return doubleRedirectionPointer; - } - - // Make copy of the variable pointer so we're not using the value direct from - // the runtime. Temporary must be local to the current scope. - else { - return new VariablePointerValue(varPointer.variableName, contextIndex); - } - } - }, { - key: 'GetContextIndexOfVariableNamed', - value: function GetContextIndexOfVariableNamed(varName) { - if (this._globalVariables[varName]) return 0; - - return this._callStack.currentElementIndex; - } - //the original code uses a magic getter and setter for global variables, allowing things like variableState['varname]. This is not quite possible in js without a Proxy, so it is replaced with this $ function. - - }, { - key: '$', - value: function $(variableName, value) { - if (typeof value === 'undefined') { - var varContents = this._globalVariables[variableName]; - if (typeof varContents !== 'undefined') - // return (varContents as Runtime.Value).valueObject; - return varContents.valueObject;else return null; - } else { - if (typeof this._globalVariables[variableName] === 'undefined') { - throw new StoryException("Variable '" + variableName + "' doesn't exist, so can't be set."); - } - - var val = Value.Create(value); - if (val == null) { - if (value == null) { - throw new StoryException("Cannot pass null to VariableState"); - } else { - throw new StoryException("Invalid value passed to VariableState: " + value.toString()); - } - } - - this.SetGlobal(variableName, val); - } - } - }, { - key: 'callStack', - get: function get$$1() { - return this._callStack; - }, - set: function set$$1(callStack) { - this._callStack = callStack; - } - }, { - key: 'batchObservingVariableChanges', - get: function get$$1() { - return this._batchObservingVariableChanges; - }, - set: function set$$1(value) { - var _this2 = this; - - value = !!value; - this._batchObservingVariableChanges = value; - if (value) { - this._changedVariables = []; - } - - // Finished observing variables in a batch - now send - // notifications for changed variables all in one go. - else { - if (this._changedVariables != null) { - this._changedVariables.forEach(function (variableName) { - var currentValue = _this2._globalVariables[variableName]; - _this2.variableChangedEvent(variableName, currentValue); - }); - } - - this._changedVariables = null; - } - } - }, { - key: 'jsonToken', - get: function get$$1() { - return JsonSerialisation.DictionaryRuntimeObjsToJObject(this._globalVariables); - }, - set: function set$$1(value) { - this._globalVariables = JsonSerialisation.JObjectToDictionaryRuntimeObjs(value); - } - }]); - return VariablesState; - }(); - - //Taken from https://gist.github.com/blixt/f17b47c62508be59987b - //Ink uses a seedable PRNG of which there is none in native javascript. - var PRNG = function () { - function PRNG(seed) { - classCallCheck(this, PRNG); - - this._seed = seed % 2147483647; - if (this._seed <= 0) this._seed += 2147483646; - } - - createClass(PRNG, [{ - key: "next", - value: function next() { - return this._seed = this._seed * 16807 % 2147483647; - } - }, { - key: "nextFloat", - value: function nextFloat() { - return (this.next() - 1) / 2147483646; - } - }]); - return PRNG; - }(); - - var StoryState = function () { - function StoryState(story) { - classCallCheck(this, StoryState); - - //actual constructor - this.story = story; - - this._outputStream = []; - this._outputStreamTextDirty = true; - this._outputStreamTagsDirty = true; - this.OutputStreamDirty(); - - this._evaluationStack = []; - - this.callStack = new CallStack(story.rootContentContainer); - this._variablesState = new VariablesState(this.callStack, story.listDefinitions); - - this._visitCounts = {}; - this._turnIndices = {}; - this._currentTurnIndex = -1; - - this.divertedTargetObject = null; - - var timeSeed = new Date().getTime(); - this.storySeed = new PRNG(timeSeed).next() % 100; - this.previousRandom = 0; - - this._currentChoices = []; - this._currentText = null; - this._currentTags = null; - this._currentErrors = null; - - this.didSafeExit = false; - - this._isExternalFunctionEvaluation = false; - this._originalCallstack = null; - this._originalEvaluationStackHeight = 0; - - this.GoToStart(); - } - - createClass(StoryState, [{ - key: 'MatchRightGlueForLeftGlue', - value: function MatchRightGlueForLeftGlue(leftGlue) { - if (!leftGlue.isLeft) return null; - - for (var i = this._outputStream.length - 1; i >= 0; i--) { - var c = this._outputStream[i]; - // var g = c as Glue; - var g = c; - if (g instanceof Glue && g.isRight && g.parent == leftGlue.parent) { - return g; - } else if (c instanceof ControlCommand) // e.g. BeginString - break; - } - - return null; - } - }, { - key: 'GoToStart', - value: function GoToStart() { - this.callStack.currentElement.currentContainer = this.story.mainContentContainer; - this.callStack.currentElement.currentContentIndex = 0; - } - }, { - key: 'ResetErrors', - value: function ResetErrors() { - this._currentErrors = null; - } - }, { - key: 'ResetOutput', - value: function ResetOutput() { - this._outputStream.length = 0; - this.OutputStreamDirty(); - } - }, { - key: 'PushEvaluationStack', - value: function PushEvaluationStack(obj) { - var _this = this; - - // var listValue = obj as ListValue; - var listValue = obj; - if (listValue instanceof ListValue) { - - // Update origin when list is has something to indicate the list origin - var rawList = listValue.value; - var names = rawList.originNames; - if (names != null) { - var origins = []; - - names.forEach(function (n) { - var def = null; - def = _this.story.listDefinitions.TryGetDefinition(n, def); - if (origins.indexOf(def) < 0) origins.push(def); - }); - - rawList.origins = origins; - } - } - - this.evaluationStack.push(obj); - } - }, { - key: 'PopEvaluationStack', - value: function PopEvaluationStack(numberOfObjects) { - if (!numberOfObjects) { - var obj = this.evaluationStack.pop(); - return obj; - } else { - if (numberOfObjects > this.evaluationStack.length) { - throw "trying to pop too many objects"; - } - - var popped = this.evaluationStack.splice(this.evaluationStack.length - numberOfObjects, numberOfObjects); - return popped; - } - } - }, { - key: 'PeekEvaluationStack', - value: function PeekEvaluationStack() { - return this.evaluationStack[this.evaluationStack.length - 1]; - } - }, { - key: 'PushToOutputStream', - value: function PushToOutputStream(obj) { - var _this2 = this; - - // var text = obj as StringValue; - var text = obj; - if (text instanceof StringValue) { - var listText = this.TrySplittingHeadTailWhitespace(text); - if (listText != null) { - listText.forEach(function (textObj) { - _this2.PushToOutputStreamIndividual(textObj); - }); - return; - } - } - - this.PushToOutputStreamIndividual(obj); - this.OutputStreamDirty(); - } - }, { - key: 'TrySplittingHeadTailWhitespace', - value: function TrySplittingHeadTailWhitespace(single) { - var str = single.value; - - var headFirstNewlineIdx = -1; - var headLastNewlineIdx = -1; - for (var i = 0; i < str.length; ++i) { - var c = str[i]; - if (c == '\n') { - if (headFirstNewlineIdx == -1) headFirstNewlineIdx = i; - headLastNewlineIdx = i; - } else if (c == ' ' || c == '\t') continue;else break; - } - - var tailLastNewlineIdx = -1; - var tailFirstNewlineIdx = -1; - for (var i = 0; i < str.length; ++i) { - var c = str[i]; - if (c == '\n') { - if (tailLastNewlineIdx == -1) tailLastNewlineIdx = i; - tailFirstNewlineIdx = i; - } else if (c == ' ' || c == '\t') continue;else break; - } - - // No splitting to be done? - if (headFirstNewlineIdx == -1 && tailLastNewlineIdx == -1) return null; - - var listTexts = []; - var innerStrStart = 0; - var innerStrEnd = str.length; - - if (headFirstNewlineIdx != -1) { - if (headFirstNewlineIdx > 0) { - var leadingSpaces = str.substring(0, headFirstNewlineIdx); - listTexts.push(leadingSpaces); - } - listTexts.push(new StringValue("\n")); - innerStrStart = headLastNewlineIdx + 1; - } - - if (tailLastNewlineIdx != -1) { - innerStrEnd = tailFirstNewlineIdx; - } - - if (innerStrEnd > innerStrStart) { - var innerStrText = str.substring(innerStrStart, innerStrEnd - innerStrStart); - listTexts.push(new StringValue(innerStrText)); - } - - if (tailLastNewlineIdx != -1 && tailFirstNewlineIdx > headLastNewlineIdx) { - listTexts.push(new StringValue("\n")); - if (tailLastNewlineIdx < str.length - 1) { - var numSpaces = str.Length - tailLastNewlineIdx - 1; - var trailingSpaces = new StringValue(str.substring(tailLastNewlineIdx + 1, numSpaces)); - listTexts.push(trailingSpaces); - } - } - - return listTexts; - } - }, { - key: 'PushToOutputStreamIndividual', - value: function PushToOutputStreamIndividual(obj) { - var glue = obj; - var text = obj; - - var includeInOutput = true; - - if (glue instanceof Glue) { - // Found matching left-glue for right-glue? Close it. - var existingRightGlue = this.currentRightGlue; - var foundMatchingLeftGlue = !!(glue.isLeft && existingRightGlue && glue.parent == existingRightGlue.parent); - var matchingRightGlue = null; - - if (glue.isLeft) matchingRightGlue = this.MatchRightGlueForLeftGlue(glue); - - // Left/Right glue is auto-generated for inline expressions - // where we want to absorb newlines but only in a certain direction. - // "Bi" glue is written by the user in their ink with <> - if (glue.isLeft || glue.isBi) { - this.TrimNewlinesFromOutputStream(matchingRightGlue); - } - - includeInOutput = glue.isBi || glue.isRight; - } else if (text instanceof StringValue) { - - if (this.currentGlueIndex != -1) { - - // Absorb any new newlines if there's existing glue - // in the output stream. - // Also trim any extra whitespace (spaces/tabs) if so. - if (text.isNewline) { - this.TrimFromExistingGlue(); - includeInOutput = false; - } - - // Able to completely reset when - else if (text.isNonWhitespace) { - this.RemoveExistingGlue(); - } - } else if (text.isNewline) { - if (this.outputStreamEndsInNewline || !this.outputStreamContainsContent) includeInOutput = false; - } - } - - if (includeInOutput) { - this._outputStream.push(obj); - this.OutputStreamDirty(); - } - } - }, { - key: 'TrimNewlinesFromOutputStream', - value: function TrimNewlinesFromOutputStream(rightGlueToStopAt) { - var removeWhitespaceFrom = -1; - var rightGluePos = -1; - var foundNonWhitespace = false; - - // Work back from the end, and try to find the point where - // we need to start removing content. There are two ways: - // - Start from the matching right-glue (because we just saw a left-glue) - // - Simply work backwards to find the first newline in a string of whitespace - var i = this._outputStream.length - 1; - while (i >= 0) { - var obj = this._outputStream[i]; - // var cmd = obj as ControlCommand; - var cmd = obj; - // var txt = obj as StringValue; - var txt = obj; - // var glue = obj as Glue; - var glue = obj; - - if (cmd instanceof ControlCommand || txt instanceof StringValue && txt.isNonWhitespace) { - foundNonWhitespace = true; - if (rightGlueToStopAt == null) break; - } else if (rightGlueToStopAt && glue instanceof Glue && glue == rightGlueToStopAt) { - rightGluePos = i; - break; - } else if (txt instanceof StringValue && txt.isNewline && !foundNonWhitespace) { - removeWhitespaceFrom = i; - } - i--; - } - - // Remove the whitespace - if (removeWhitespaceFrom >= 0) { - i = removeWhitespaceFrom; - while (i < this._outputStream.length) { - // var text = _outputStream [i] as StringValue; - var text = this._outputStream[i]; - if (text instanceof StringValue) { - this._outputStream.splice(i, 1); - } else { - i++; - } - } - } - - if (rightGlueToStopAt && rightGluePos > -1) { - i = rightGluePos; - while (i < this._outputStream.length) { - if (this._outputStream[i] instanceof Glue && this._outputStream[i].isRight) { - this.outputStream.splice(i, 1); - } else { - i++; - } - } - } - - this.OutputStreamDirty(); - } - }, { - key: 'TrimFromExistingGlue', - value: function TrimFromExistingGlue() { - var i = this.currentGlueIndex; - while (i < this._outputStream.length) { - // var txt = _outputStream [i] as StringValue; - var txt = this._outputStream[i]; - if (txt instanceof StringValue && !txt.isNonWhitespace) this._outputStream.splice(i, 1);else i++; - } - - this.OutputStreamDirty(); - } - }, { - key: 'RemoveExistingGlue', - value: function RemoveExistingGlue() { - for (var i = this._outputStream.length - 1; i >= 0; i--) { - var c = this._outputStream[i]; - if (c instanceof Glue) { - this._outputStream.splice(i, 1); - } else if (c instanceof ControlCommand) { - // e.g. BeginString - break; - } - } - - this.OutputStreamDirty(); - } - }, { - key: 'ForceEnd', - value: function ForceEnd() { - while (this.callStack.canPopThread) { - this.callStack.PopThread(); - }while (this.callStack.canPop) { - this.callStack.Pop(); - }this._currentChoices.length = 0; - - this.currentContentObject = null; - this.previousContentObject = null; - - this.didSafeExit = true; - } - }, { - key: 'SetChosenPath', - value: function SetChosenPath(path) { - // Changing direction, assume we need to clear current set of choices - this._currentChoices.length = 0; - - this.currentPath = path; - - this._currentTurnIndex++; - } - }, { - key: 'StartExternalFunctionEvaluation', - value: function StartExternalFunctionEvaluation(funcContainer, args) { - // We'll start a new callstack, so keep hold of the original, - // as well as the evaluation stack so we know if the function - // returned something - this._originalCallstack = this.callStack; - this._originalEvaluationStackHeight = this.evaluationStack.length; - - // Create a new base call stack element. - this.callStack = new CallStack(funcContainer); - this.callStack.currentElement.type = PushPopType.Function; - - this._variablesState.callStack = this.callStack; - - // By setting ourselves in external function evaluation mode, - // we're saying it's okay to end the flow without a Done or End, - // but with a ~ return instead. - this._isExternalFunctionEvaluation = true; - - this.PassArgumentsToEvaluationStack(args); - } - }, { - key: 'PassArgumentsToEvaluationStack', - value: function PassArgumentsToEvaluationStack(args) { - // Pass arguments onto the evaluation stack - if (args != null) { - for (var i = 0; i < args.length; i++) { - if (!(typeof args[i] === 'number' || typeof args[i] === 'string')) { - throw "ink arguments when calling EvaluateFunction / ChoosePathStringWithParameters must be int, float or string"; - } - - this.PushEvaluationStack(Value.Create(args[i])); - } - } - } - }, { - key: 'TryExitExternalFunctionEvaluation', - value: function TryExitExternalFunctionEvaluation() { - if (this._isExternalFunctionEvaluation && this.callStack.elements.length == 1 && this.callStack.currentElement.type == PushPopType.Function) { - this.currentContentObject = null; - this.didSafeExit = true; - return true; - } - - return false; - } - }, { - key: 'CompleteExternalFunctionEvaluation', - value: function CompleteExternalFunctionEvaluation() { - // Do we have a returned value? - // Potentially pop multiple values off the stack, in case we need - // to clean up after ourselves (e.g. caller of EvaluateFunction may - // have passed too many arguments, and we currently have no way to check for that) - var returnedObj = null; - while (this.evaluationStack.length > this._originalEvaluationStackHeight) { - var poppedObj = this.PopEvaluationStack(); - if (returnedObj == null) returnedObj = poppedObj; - } - - // Restore our own state - this.callStack = this._originalCallstack; - this._originalCallstack = null; - this._originalEvaluationStackHeight = 0; - - this._variablesState.callStack = this.callStack; - - if (returnedObj) { - if (returnedObj instanceof Void) return null; - - // Some kind of value, if not void - // var returnVal = returnedObj as Runtime.Value; - var returnVal = returnedObj; - - // DivertTargets get returned as the string of components - // (rather than a Path, which isn't public) - if (returnVal.valueType == ValueType.DivertTarget) { - return returnVal.valueObject.toString(); - } - - // Other types can just have their exact object type: - // int, float, string. VariablePointers get returned as strings. - return returnVal.valueObject; - } - - return null; - } - }, { - key: 'AddError', - value: function AddError(message) { - if (this._currentErrors == null) { - this._currentErrors = []; - } - - this._currentErrors.push(message); - } - }, { - key: 'OutputStreamDirty', - value: function OutputStreamDirty() { - this._outputStreamTextDirty = true; - this._outputStreamTagsDirty = true; - } - }, { - key: 'VisitCountAtPathString', - value: function VisitCountAtPathString(pathString) { - var visitCountOut; - if (visitCountOut = this.visitCounts[pathString]) return visitCountOut; - - return 0; - } - }, { - key: 'Copy', - value: function Copy() { - var copy = new StoryState(this.story); - - copy.outputStream.push.apply(copy.outputStream, this._outputStream); - this.OutputStreamDirty(); - - copy._currentChoices.push.apply(copy._currentChoices, this._currentChoices); - - if (this.hasError) { - copy.currentErrors = []; - copy.currentErrors.push.apply(copy.currentErrors, this.currentErrors); - } - - copy.callStack = new CallStack(this.callStack); - if (this._originalCallstack) copy._originalCallstack = new CallStack(this._originalCallstack); - - copy._variablesState = new VariablesState(copy.callStack, this.story.listDefinitions); - copy.variablesState.CopyFrom(this.variablesState); - - copy.evaluationStack.push.apply(copy.evaluationStack, this.evaluationStack); - copy._originalEvaluationStackHeight = this._originalEvaluationStackHeight; - - if (this.divertedTargetObject != null) copy.divertedTargetObject = this.divertedTargetObject; - - copy.previousContentObject = this.previousContentObject; - - copy._isExternalFunctionEvaluation = this._isExternalFunctionEvaluation; - - copy._visitCounts = {}; - for (var keyValue in this._visitCounts) { - copy._visitCounts[keyValue] = this._visitCounts[keyValue]; - } - copy._turnIndices = {}; - for (var keyValue in this._turnIndices) { - copy._turnIndices[keyValue] = this._turnIndices[keyValue]; - } - - copy._currentTurnIndex = this.currentTurnIndex; - copy.storySeed = this.storySeed; - copy.previousRandom = this.previousRandom; - - copy.didSafeExit = this.didSafeExit; - - return copy; - } - }, { - key: 'toJson', - value: function toJson(indented) { - return JSON.stringify(this.jsonToken, null, indented ? 2 : 0); - } - }, { - key: 'LoadJson', - value: function LoadJson(jsonString) { - this.jsonToken = JSON.parse(jsonString); - } - }, { - key: 'currentChoices', - get: function get$$1() { - // If we can continue generating text content rather than choices, - // then we reflect the choice list as being empty, since choices - // should always come at the end. - if (this.canContinue) return []; - return this._currentChoices; - } - }, { - key: 'generatedChoices', - get: function get$$1() { - return this._currentChoices; - } - }, { - key: 'currentErrors', - get: function get$$1() { - return this._currentErrors; - } - }, { - key: 'visitCounts', - get: function get$$1() { - return this._visitCounts; - } - }, { - key: 'turnIndices', - get: function get$$1() { - return this._turnIndices; - } - }, { - key: 'currentTurnIndex', - get: function get$$1() { - return this._currentTurnIndex; - } - }, { - key: 'variablesState', - get: function get$$1() { - return this._variablesState; - } - }, { - key: 'currentContentObject', - get: function get$$1() { - return this.callStack.currentElement.currentObject; - }, - set: function set$$1(value) { - this.callStack.currentElement.currentObject = value; - } - }, { - key: 'canContinue', - get: function get$$1() { - return this.currentContentObject != null && !this.hasError; - } - }, { - key: 'hasError', - get: function get$$1() { - return this.currentErrors != null && this.currentErrors.length > 0; - } - }, { - key: 'inExpressionEvaluation', - get: function get$$1() { - return this.callStack.currentElement.inExpressionEvaluation; - }, - set: function set$$1(value) { - this.callStack.currentElement.inExpressionEvaluation = value; - } - }, { - key: 'evaluationStack', - get: function get$$1() { - return this._evaluationStack; - } - }, { - key: 'outputStreamEndsInNewline', - get: function get$$1() { - if (this._outputStream.length > 0) { - - for (var i = this._outputStream.length - 1; i >= 0; i--) { - var obj = this._outputStream[i]; - if (obj instanceof ControlCommand) // e.g. BeginString - break; - var text = this._outputStream[i]; - if (text instanceof StringValue) { - if (text.isNewline) return true;else if (text.isNonWhitespace) break; - } - } - } - - return false; - } - }, { - key: 'outputStreamContainsContent', - get: function get$$1() { - for (var i = 0; i < this._outputStream.length; i++) { - if (this._outputStream[i] instanceof StringValue) return true; - } - return false; - } - }, { - key: 'currentGlueIndex', - get: function get$$1() { - for (var i = this._outputStream.length - 1; i >= 0; i--) { - var c = this._outputStream[i]; - // var glue = c as Glue; - var glue = c; - if (glue instanceof Glue) return i;else if (c instanceof ControlCommand) // e.g. BeginString - break; - } - return -1; - } - }, { - key: 'currentRightGlue', - get: function get$$1() { - for (var i = this._outputStream.length - 1; i >= 0; i--) { - var c = this._outputStream[i]; - // var glue = c as Glue; - var glue = c; - if (glue instanceof Glue && glue.isRight) return glue;else if (c instanceof ControlCommand) // e.g. BeginString - break; - } - return null; - } - }, { - key: 'inStringEvaluation', - get: function get$$1() { - for (var i = this._outputStream.length - 1; i >= 0; i--) { - // var cmd = this._outputStream[i] as ControlCommand; - var cmd = this._outputStream[i]; - if (cmd instanceof ControlCommand && cmd.commandType == ControlCommand.CommandType.BeginString) { - return true; - } - } - - return false; - } - }, { - key: 'currentText', - get: function get$$1() { - if (this._outputStreamTextDirty) { - var sb = new StringBuilder(); - - this._outputStream.forEach(function (outputObj) { - // var textContent = outputObj as StringValue; - var textContent = outputObj; - if (textContent instanceof StringValue) { - sb.Append(textContent.value); - } - }); - - this._currentText = sb.toString(); - this._outputStreamTextDirty = false; - } - - return this._currentText; - } - }, { - key: 'currentTags', - get: function get$$1() { - var _this3 = this; - - if (this._outputStreamTagsDirty) { - this._currentTags = []; - - this._outputStream.forEach(function (outputObj) { - // var tag = outputObj as Tag; - var tag = outputObj; - if (tag instanceof Tag) { - _this3._currentTags.push(tag.text); - } - }); - - this._outputStreamTagsDirty = false; - } - - return this._currentTags; - } - }, { - key: 'outputStream', - get: function get$$1() { - return this._outputStream; - } - }, { - key: 'currentPath', - get: function get$$1() { - if (this.currentContentObject == null) return null; - - return this.currentContentObject.path; - }, - set: function set$$1(value) { - if (value != null) this.currentContentObject = this.story.ContentAtPath(value);else this.currentContentObject = null; - } - }, { - key: 'currentContainer', - get: function get$$1() { - return this.callStack.currentElement.currentContainer; - } - }, { - key: 'previousContentObject', - get: function get$$1() { - return this.callStack.currentThread.previousContentObject; - }, - set: function set$$1(value) { - this.callStack.currentThread.previousContentObject = value; - } - }, { - key: 'callstackDepth', - get: function get$$1() { - return this.callStack.depth; - } - }, { - key: 'jsonToken', - get: function get$$1() { - var _this4 = this; - - var obj = {}; - - var choiceThreads = null; - this._currentChoices.forEach(function (c) { - c.originalChoicePath = c.choicePoint.path.componentsString; - c.originalThreadIndex = c.threadAtGeneration.threadIndex; - - if (_this4.callStack.ThreadWithIndex(c.originalThreadIndex) == null) { - if (choiceThreads == null) choiceThreads = {}; - - choiceThreads[c.originalThreadIndex.toString()] = c.threadAtGeneration.jsonToken; - } - }); - - if (this.choiceThreads != null) obj["choiceThreads"] = this.choiceThreads; - - obj["callstackThreads"] = this.callStack.GetJsonToken(); - obj["variablesState"] = this.variablesState.jsonToken; - - obj["evalStack"] = JsonSerialisation.ListToJArray(this.evaluationStack); - - obj["outputStream"] = JsonSerialisation.ListToJArray(this._outputStream); - - obj["currentChoices"] = JsonSerialisation.ListToJArray(this._currentChoices); - - if (this.divertedTargetObject != null) obj["currentDivertTarget"] = this.divertedTargetObject.path.componentsString; - - obj["visitCounts"] = JsonSerialisation.IntDictionaryToJObject(this.visitCounts); - obj["turnIndices"] = JsonSerialisation.IntDictionaryToJObject(this.turnIndices); - obj["turnIdx"] = this.currentTurnIndex; - obj["storySeed"] = this.storySeed; - - obj["inkSaveVersion"] = StoryState.kInkSaveStateVersion; - - // Not using this right now, but could do in future. - obj["inkFormatVersion"] = this.story.inkVersionCurrent; - - return obj; - }, - set: function set$$1(value) { - var _this5 = this; - - var jObject = value; - - var jSaveVersion = jObject["inkSaveVersion"]; - if (jSaveVersion == null) { - throw new StoryException("ink save format incorrect, can't load."); - } else if (parseInt(jSaveVersion) < StoryState.kMinCompatibleLoadVersion) { - throw new StoryException("Ink save format isn't compatible with the current version (saw '" + jSaveVersion + "', but minimum is " + StoryState.kMinCompatibleLoadVersion + "), so can't load."); - } - - this.callStack.SetJsonToken(jObject["callstackThreads"], this.story); - this.variablesState.jsonToken = jObject["variablesState"]; - - this._evaluationStack = JsonSerialisation.JArrayToRuntimeObjList(jObject["evalStack"]); - - this._outputStream = JsonSerialisation.JArrayToRuntimeObjList(jObject["outputStream"]); - this.OutputStreamDirty(); - - // currentChoices = Json.JArrayToRuntimeObjList((JArray)jObject ["currentChoices"]); - this._currentChoices = JsonSerialisation.JArrayToRuntimeObjList(jObject["currentChoices"]); - - var currentDivertTargetPath = jObject["currentDivertTarget"]; - if (currentDivertTargetPath != null) { - var divertPath = new Path$1(currentDivertTargetPath.toString()); - this.divertedTargetObject = this.story.ContentAtPath(divertPath); - } - - this._visitCounts = JsonSerialisation.JObjectToIntDictionary(jObject["visitCounts"]); - this._turnIndices = JsonSerialisation.JObjectToIntDictionary(jObject["turnIndices"]); - this._currentTurnIndex = parseInt(jObject["turnIdx"]); - this.storySeed = parseInt(jObject["storySeed"]); - - // var jChoiceThreads = jObject["choiceThreads"] as JObject; - var jChoiceThreads = jObject["choiceThreads"]; - - this._currentChoices.forEach(function (c) { - c.choicePoint = _this5.story.ContentAtPath(new Path$1(c.originalChoicePath)); - - var foundActiveThread = _this5.callStack.ThreadWithIndex(c.originalThreadIndex); - if (foundActiveThread != null) { - c.threadAtGeneration = foundActiveThread; - } else { - var jSavedChoiceThread = jChoiceThreads[c.originalThreadIndex.toString()]; - c.threadAtGeneration = new CallStack.Thread(jSavedChoiceThread, _this5.story); - } - }); - } - }]); - return StoryState; - }(); - - StoryState.kInkSaveStateVersion = 7; - StoryState.kMinCompatibleLoadVersion = 6; - - if (!Number.isInteger) { - Number.isInteger = function isInteger(nVal) { - return typeof nVal === "number" && isFinite(nVal) && nVal > -9007199254740992 && nVal < 9007199254740992 && Math.floor(nVal) === nVal; - }; - } - - var Story = function (_InkObject) { - inherits(Story, _InkObject); - - function Story(jsonString, lists) { - classCallCheck(this, Story); - - var _this = possibleConstructorReturn(this, (Story.__proto__ || Object.getPrototypeOf(Story)).call(this)); - - lists = lists || null; - - _this.inkVersionCurrent = 17; - _this.inkVersionMinimumCompatible = 16; - - _this._variableObservers = null; - _this._externals = {}; - _this._prevContainerSet = null; - _this._listDefinitions = null; - - if (jsonString instanceof Container) { - _this._mainContentContainer = jsonString; - - if (lists != null) _this._listDefinitions = new ListDefinitionsOrigin(lists); - } else { - //the original version only accepts a string as a constructor, but this is javascript and it's almost easier to get a JSON value than a string, so we're silently accepting both - var rootObject = typeof jsonString === 'string' ? JSON.parse(jsonString) : jsonString; - - var versionObj = rootObject["inkVersion"]; - if (versionObj == null) throw "ink version number not found. Are you sure it's a valid .ink.json file?"; - - var formatFromFile = parseInt(versionObj); - if (formatFromFile > _this.inkVersionCurrent) { - throw "Version of ink used to build story was newer than the current verison of the engine"; - } else if (formatFromFile < _this.inkVersionMinimumCompatible) { - throw "Version of ink used to build story is too old to be loaded by this verison of the engine"; - } else if (formatFromFile != _this.inkVersionCurrent) { - console.warn("WARNING: Version of ink used to build story doesn't match current version of engine. Non-critical, but recommend synchronising."); - } - - var rootToken = rootObject["root"]; - if (rootToken == null) throw "Root node for ink not found. Are you sure it's a valid .ink.json file?"; - - var listDefsObj; - if (listDefsObj = rootObject["listDefs"]) { - _this._listDefinitions = JsonSerialisation.JTokenToListDefinitions(listDefsObj); - } - - _this._mainContentContainer = JsonSerialisation.JTokenToRuntimeObject(rootToken); - - _this._hasValidatedExternals = null; - _this.allowExternalFunctionFallbacks = false; - - _this.ResetState(); - } - return _this; - } - - createClass(Story, [{ - key: 'ToJsonString', - value: function ToJsonString() { - var rootContainerJsonList = JsonSerialisation.RuntimeObjectToJToken(this._mainContentContainer); - - var rootObject = {}; - rootObject["inkVersion"] = this.inkVersionCurrent; - rootObject["root"] = rootContainerJsonList; - - if (this._listDefinitions != null) rootObject["listDefs"] = JsonSerialisation.ListDefinitionsToJToken(this._listDefinitions); - - return JSON.stringify(rootObject); - } - }, { - key: 'ResetState', - value: function ResetState() { - this._state = new StoryState(this); - this._state.variablesState.ObserveVariableChange(this.VariableStateDidChangeEvent.bind(this)); - - this.ResetGlobals(); - } - }, { - key: 'ResetErrors', - value: function ResetErrors() { - this._state.ResetErrors(); - } - }, { - key: 'ResetCallstack', - value: function ResetCallstack() { - this._state.ForceEnd(); - } - }, { - key: 'ResetGlobals', - value: function ResetGlobals() { - if (this._mainContentContainer.namedContent["global decl"]) { - var originalPath = this.state.currentPath; - - this.ChoosePathString("global decl"); - - // Continue, but without validating external bindings, - // since we may be doing this reset at initialisation time. - this.ContinueInternal(); - - this.state.currentPath = originalPath; - } - } - }, { - key: 'Continue', - value: function Continue() { - if (!this._hasValidatedExternals) this.ValidateExternalBindings(); - - return this.ContinueInternal(); - } - }, { - key: 'ContinueInternal', - value: function ContinueInternal() { - if (!this.canContinue) { - throw new StoryException("Can't continue - should check canContinue before calling Continue"); - } - - this._state.ResetOutput(); - - this._state.didSafeExit = false; - - this._state.variablesState.batchObservingVariableChanges = true; - - try { - - var stateAtLastNewline = null; - - // The basic algorithm here is: - // - // do { Step() } while( canContinue && !outputStreamEndsInNewline ); - // - // But the complexity comes from: - // - Stepping beyond the newline in case it'll be absorbed by glue later - // - Ensuring that non-text content beyond newlines are generated - i.e. choices, - // which are actually built out of text content. - // So we have to take a snapshot of the state, continue prospectively, - // and rewind if necessary. - // This code is slightly fragile :-/ - // - - do { - - // Run main step function (walks through content) - this.Step(); - - // Run out of content and we have a default invisible choice that we can follow? - if (!this.canContinue) { - this.TryFollowDefaultInvisibleChoice(); - } - - // Don't save/rewind during string evaluation, which is e.g. used for choices - if (!this.state.inStringEvaluation) { - - // We previously found a newline, but were we just double checking that - // it wouldn't immediately be removed by glue? - if (stateAtLastNewline != null) { - - // Cover cases that non-text generated content was evaluated last step - var currText = this.currentText; - var prevTextLength = stateAtLastNewline.currentText.length; - var prevTagCount = stateAtLastNewline.currentTags.length; - - // Output has been extended? - if (currText !== stateAtLastNewline.currentText || prevTagCount != this.currentTags.length) { - - // Original newline still exists? - if (currText.length >= prevTextLength && currText[prevTextLength - 1] == '\n') { - - this.RestoreStateSnapshot(stateAtLastNewline); - break; - } - - // Newline that previously existed is no longer valid - e.g. - // glue was encounted that caused it to be removed. - else { - stateAtLastNewline = null; - } - } - } - - // Current content ends in a newline - approaching end of our evaluation - if (this.state.outputStreamEndsInNewline) { - - // If we can continue evaluation for a bit: - // Create a snapshot in case we need to rewind. - // We're going to continue stepping in case we see glue or some - // non-text content such as choices. - if (this.canContinue) { - // Don't bother to record the state beyond the current newline. - // e.g.: - // Hello world\n // record state at the end of here - // ~ complexCalculation() // don't actually need this unless it generates text - if (stateAtLastNewline == null) { - stateAtLastNewline = this.StateSnapshot(); - } - } - - // Can't continue, so we're about to exit - make sure we - // don't have an old state hanging around. - else { - stateAtLastNewline = null; - } - } - } - } while (this.canContinue); - - // Need to rewind, due to evaluating further than we should? - if (stateAtLastNewline != null) { - this.RestoreStateSnapshot(stateAtLastNewline); - } - - // Finished a section of content / reached a choice point? - if (!this.canContinue) { - - if (this.state.callStack.canPopThread) { - this.Error("Thread available to pop, threads should always be flat by the end of evaluation?"); - } - - if (this.state.generatedChoices.length == 0 && !this.state.didSafeExit && this._temporaryEvaluationContainer == null) { - if (this.state.callStack.CanPop(PushPopType.Tunnel)) { - this.Error("unexpectedly reached end of content. Do you need a '->->' to return from a tunnel?"); - } else if (this.state.callStack.CanPop(PushPopType.Function)) { - this.Error("unexpectedly reached end of content. Do you need a '~ return'?"); - } else if (!this.state.callStack.canPop) { - this.Error("ran out of content. Do you need a '-> DONE' or '-> END'?"); - } else { - this.Error("unexpectedly reached end of content for unknown reason. Please debug compiler!"); - } - } - } - } catch (e) { - throw e; - this.AddError(e.Message, e.useEndLineNumber); - } finally { - this.state.didSafeExit = false; - - this._state.variablesState.batchObservingVariableChanges = false; - } - - return this.currentText; - } - }, { - key: 'ContinueMaximally', - value: function ContinueMaximally() { - var sb = new StringBuilder(); - - while (this.canContinue) { - sb.Append(this.Continue()); - } - - return sb.toString(); - } - }, { - key: 'ContentAtPath', - value: function ContentAtPath(path) { - return this.mainContentContainer.ContentAtPath(path); - } - }, { - key: 'StateSnapshot', - value: function StateSnapshot() { - return this.state.Copy(); - } - }, { - key: 'RestoreStateSnapshot', - value: function RestoreStateSnapshot(state) { - this._state = state; - } - }, { - key: 'Step', - value: function Step() { - var shouldAddToStream = true; - - // Get current content - var currentContentObj = this.state.currentContentObject; - if (currentContentObj == null) { - return; - } - // Step directly to the first element of content in a container (if necessary) - // Container currentContainer = currentContentObj as Container; - var currentContainer = currentContentObj; - while (currentContainer instanceof Container) { - - // Mark container as being entered - this.VisitContainer(currentContainer, true); - - // No content? the most we can do is step past it - if (currentContainer.content.length == 0) break; - - currentContentObj = currentContainer.content[0]; - this.state.callStack.currentElement.currentContentIndex = 0; - this.state.callStack.currentElement.currentContainer = currentContainer; - - // currentContainer = currentContentObj as Container; - currentContainer = currentContentObj; - } - currentContainer = this.state.callStack.currentElement.currentContainer; - - // Is the current content object: - // - Normal content - // - Or a logic/flow statement - if so, do it - // Stop flow if we hit a stack pop when we're unable to pop (e.g. return/done statement in knot - // that was diverted to rather than called as a function) - var isLogicOrFlowControl = this.PerformLogicAndFlowControl(currentContentObj); - - // Has flow been forced to end by flow control above? - if (this.state.currentContentObject == null) { - return; - } - - if (isLogicOrFlowControl) { - shouldAddToStream = false; - } - - // Choice with condition? - // var choicePoint = currentContentObj as ChoicePoint; - var choicePoint = currentContentObj; - if (choicePoint instanceof ChoicePoint) { - var choice = this.ProcessChoice(choicePoint); - if (choice) { - this.state.generatedChoices.push(choice); - } - - currentContentObj = null; - shouldAddToStream = false; - } - - // If the container has no content, then it will be - // the "content" itself, but we skip over it. - if (currentContentObj instanceof Container) { - shouldAddToStream = false; - } - - // Content to add to evaluation stack or the output stream - if (shouldAddToStream) { - - // If we're pushing a variable pointer onto the evaluation stack, ensure that it's specific - // to our current (possibly temporary) context index. And make a copy of the pointer - // so that we're not editing the original runtime object. - // var varPointer = currentContentObj as VariablePointerValue; - var varPointer = currentContentObj; - if (varPointer instanceof VariablePointerValue && varPointer.contextIndex == -1) { - - // Create new object so we're not overwriting the story's own data - var contextIdx = this.state.callStack.ContextForVariableNamed(varPointer.variableName); - currentContentObj = new VariablePointerValue(varPointer.variableName, contextIdx); - } - - // Expression evaluation content - if (this.state.inExpressionEvaluation) { - this.state.PushEvaluationStack(currentContentObj); - } - // Output stream content (i.e. not expression evaluation) - else { - this.state.PushToOutputStream(currentContentObj); - } - } - - // Increment the content pointer, following diverts if necessary - this.NextContent(); - - // Starting a thread should be done after the increment to the content pointer, - // so that when returning from the thread, it returns to the content after this instruction. - // var controlCmd = currentContentObj as ControlCommand; - var controlCmd = currentContentObj; - if (controlCmd instanceof ControlCommand && controlCmd.commandType == ControlCommand.CommandType.StartThread) { - this.state.callStack.PushThread(); - } - } - }, { - key: 'VisitContainer', - value: function VisitContainer(container, atStart) { - if (!container.countingAtStartOnly || atStart) { - if (container.visitsShouldBeCounted) this.IncrementVisitCountForContainer(container); - - if (container.turnIndexShouldBeCounted) this.RecordTurnIndexVisitToContainer(container); - } - } - }, { - key: 'VisitChangedContainersDueToDivert', - value: function VisitChangedContainersDueToDivert() { - var previousContentObject = this.state.previousContentObject; - var newContentObject = this.state.currentContentObject; - - if (!newContentObject) return; - - // First, find the previously open set of containers - this._prevContainerSet = []; - if (previousContentObject) { - // Container prevAncestor = previousContentObject as Container ?? previousContentObject.parent as Container; - var prevAncestor = previousContentObject instanceof Container ? previousContentObject : previousContentObject.parent; - while (prevAncestor instanceof Container) { - this._prevContainerSet.push(prevAncestor); - // prevAncestor = prevAncestor.parent as Container; - prevAncestor = prevAncestor.parent; - } - } - - // If the new object is a container itself, it will be visited automatically at the next actual - // content step. However, we need to walk up the new ancestry to see if there are more new containers - var currentChildOfContainer = newContentObject; - // Container currentContainerAncestor = currentChildOfContainer.parent as Container; - var currentContainerAncestor = currentChildOfContainer.parent; - while (currentContainerAncestor instanceof Container && this._prevContainerSet.indexOf(currentContainerAncestor) < 0) { - - // Check whether this ancestor container is being entered at the start, - // by checking whether the child object is the first. - var enteringAtStart = currentContainerAncestor.content.length > 0 && currentChildOfContainer == currentContainerAncestor.content[0]; - - // Mark a visit to this container - this.VisitContainer(currentContainerAncestor, enteringAtStart); - - currentChildOfContainer = currentContainerAncestor; - // currentContainerAncestor = currentContainerAncestor.parent as Container; - currentContainerAncestor = currentContainerAncestor.parent; - } - } - }, { - key: 'ProcessChoice', - value: function ProcessChoice(choicePoint) { - var showChoice = true; - - // Don't create choice if choice point doesn't pass conditional - if (choicePoint.hasCondition) { - var conditionValue = this.state.PopEvaluationStack(); - if (!this.IsTruthy(conditionValue)) { - showChoice = false; - } - } - - var startText = ""; - var choiceOnlyText = ""; - - if (choicePoint.hasChoiceOnlyContent) { - // var choiceOnlyStrVal = state.PopEvaluationStack () as StringValue; - var choiceOnlyStrVal = this.state.PopEvaluationStack(); - choiceOnlyText = choiceOnlyStrVal.value; - } - - if (choicePoint.hasStartContent) { - // var startStrVal = state.PopEvaluationStack () as StringValue; - var startStrVal = this.state.PopEvaluationStack(); - startText = startStrVal.value; - } - - // Don't create choice if player has already read this content - if (choicePoint.onceOnly) { - var visitCount = this.VisitCountForContainer(choicePoint.choiceTarget); - if (visitCount > 0) { - showChoice = false; - } - } - - var choice = new Choice(choicePoint); - choice.threadAtGeneration = this.state.callStack.currentThread.Copy(); - - // We go through the full process of creating the choice above so - // that we consume the content for it, since otherwise it'll - // be shown on the output stream. - if (!showChoice) { - return null; - } - - // Set final text for the choice - choice.text = startText + choiceOnlyText; - - return choice; - } - }, { - key: 'IsTruthy', - value: function IsTruthy(obj) { - var truthy = false; - if (obj instanceof Value) { - var val = obj; - - if (val instanceof DivertTargetValue) { - var divTarget = val; - this.Error("Shouldn't use a divert target (to " + divTarget.targetPath + ") as a conditional value. Did you intend a function call 'likeThis()' or a read count check 'likeThis'? (no arrows)"); - return false; - } - - return val.isTruthy; - } - return truthy; - } - }, { - key: 'PerformLogicAndFlowControl', - value: function PerformLogicAndFlowControl(contentObj) { - if (contentObj == null) { - return false; - } - - // Divert - if (contentObj instanceof Divert) { - var currentDivert = contentObj; - - if (currentDivert.isConditional) { - var conditionValue = this.state.PopEvaluationStack(); - - // False conditional? Cancel divert - if (!this.IsTruthy(conditionValue)) return true; - } - - if (currentDivert.hasVariableTarget) { - var varName = currentDivert.variableDivertName; - - var varContents = this.state.variablesState.GetVariableWithName(varName); - - if (!(varContents instanceof DivertTargetValue)) { - - // var intContent = varContents as IntValue; - var intContent = varContents; - - var errorMessage = "Tried to divert to a target from a variable, but the variable (" + varName + ") didn't contain a divert target, it "; - if (intContent instanceof IntValue && intContent.value == 0) { - errorMessage += "was empty/null (the value 0)."; - } else { - errorMessage += "contained '" + varContents + "'."; - } - - this.Error(errorMessage); - } - - var target = varContents; - this.state.divertedTargetObject = this.ContentAtPath(target.targetPath); - } else if (currentDivert.isExternal) { - this.CallExternalFunction(currentDivert.targetPathString, currentDivert.externalArgs); - return true; - } else { - this.state.divertedTargetObject = currentDivert.targetContent; - } - - if (currentDivert.pushesToStack) { - this.state.callStack.Push(currentDivert.stackPushType); - } - - if (this.state.divertedTargetObject == null && !currentDivert.isExternal) { - - // Human readable name available - runtime divert is part of a hard-written divert that to missing content - if (currentDivert && currentDivert.debugMetadata.sourceName != null) { - this.Error("Divert target doesn't exist: " + currentDivert.debugMetadata.sourceName); - } else { - this.Error("Divert resolution failed: " + currentDivert); - } - } - - return true; - } - - // Start/end an expression evaluation? Or print out the result? - else if (contentObj instanceof ControlCommand) { - var evalCommand = contentObj; - - switch (evalCommand.commandType) { - - case ControlCommand.CommandType.EvalStart: - if (this.state.inExpressionEvaluation) console.warn("Already in expression evaluation?"); - this.state.inExpressionEvaluation = true; - break; - - case ControlCommand.CommandType.EvalEnd: - if (!this.state.inExpressionEvaluation) console.warn("Not in expression evaluation mode"); - this.state.inExpressionEvaluation = false; - break; - - case ControlCommand.CommandType.EvalOutput: - - // If the expression turned out to be empty, there may not be anything on the stack - if (this.state.evaluationStack.length > 0) { - - var output = this.state.PopEvaluationStack(); - - // Functions may evaluate to Void, in which case we skip output - if (output != null && !(output instanceof Void)) { - // TODO: Should we really always blanket convert to string? - // It would be okay to have numbers in the output stream the - // only problem is when exporting text for viewing, it skips over numbers etc. - var text = new StringValue(output.toString()); - - this.state.PushToOutputStream(text); - } - } - break; - - case ControlCommand.CommandType.NoOp: - break; - - case ControlCommand.CommandType.Duplicate: - this.state.PushEvaluationStack(this.state.PeekEvaluationStack()); - break; - - case ControlCommand.CommandType.PopEvaluatedValue: - this.state.PopEvaluationStack(); - break; - - case ControlCommand.CommandType.PopFunction: - case ControlCommand.CommandType.PopTunnel: - - var popType = evalCommand.commandType == ControlCommand.CommandType.PopFunction ? PushPopType.Function : PushPopType.Tunnel; - - var overrideTunnelReturnTarget = null; - if (popType == PushPopType.Tunnel) { - var popped = this.state.PopEvaluationStack(); - // overrideTunnelReturnTarget = popped as DivertTargetValue; - overrideTunnelReturnTarget = popped; - if (overrideTunnelReturnTarget instanceof DivertTargetValue === false) { - if (popped instanceof Void === false) { - throw "Expected void if ->-> doesn't override target"; - } else { - overrideTunnelReturnTarget = null; - } - } - } - - if (this.state.TryExitExternalFunctionEvaluation()) { - break; - } else if (this.state.callStack.currentElement.type != popType || !this.state.callStack.canPop) { - - var names = {}; - names[PushPopType.Function] = "function return statement (~ return)"; - names[PushPopType.Tunnel] = "tunnel onwards statement (->->)"; - - var expected = names[this.state.callStack.currentElement.type]; - if (!this.state.callStack.canPop) expected = "end of flow (-> END or choice)"; - - var errorMsg = "Found " + names[popType] + ", when expected " + expected; - - this.Error(errorMsg); - } else { - this.state.callStack.Pop(); - - if (overrideTunnelReturnTarget) this.state.divertedTargetObject = this.ContentAtPath(overrideTunnelReturnTarget.targetPath); - } - break; - - case ControlCommand.CommandType.BeginString: - this.state.PushToOutputStream(evalCommand); - - if (!this.state.inExpressionEvaluation) console.warn("Expected to be in an expression when evaluating a string"); - this.state.inExpressionEvaluation = false; - break; - - case ControlCommand.CommandType.EndString: - - var contentStackForString = []; - - var outputCountConsumed = 0; - for (var i = this.state.outputStream.length - 1; i >= 0; --i) { - var obj = this.state.outputStream[i]; - - outputCountConsumed++; - - // var command = obj as ControlCommand; - var command = obj; - if (command instanceof ControlCommand && command.commandType == ControlCommand.CommandType.BeginString) { - break; - } - - if (obj instanceof StringValue) contentStackForString.push(obj); - } - - // Consume the content that was produced for this string - this.state.outputStream.splice(this.state.outputStream.length - outputCountConsumed, outputCountConsumed); - - //the C# version uses a Stack for contentStackForString, but we're using a simple array, so we need to reverse it before using it - contentStackForString = contentStackForString.reverse(); - - // Build string out of the content we collected - var sb = new StringBuilder(); - contentStackForString.forEach(function (c) { - sb.Append(c.toString()); - }); - - // Return to expression evaluation (from content mode) - this.state.inExpressionEvaluation = true; - this.state.PushEvaluationStack(new StringValue(sb.toString())); - break; - - case ControlCommand.CommandType.ChoiceCount: - var choiceCount = this.state.generatedChoices.length; - this.state.PushEvaluationStack(new IntValue(choiceCount)); - break; - - case ControlCommand.CommandType.TurnsSince: - case ControlCommand.CommandType.ReadCount: - var target = this.state.PopEvaluationStack(); - if (!(target instanceof DivertTargetValue)) { - var extraNote = ""; - if (target instanceof IntValue) extraNote = ". Did you accidentally pass a read count ('knot_name') instead of a target ('-> knot_name')?"; - this.Error("TURNS_SINCE / READ_COUNT expected a divert target (knot, stitch, label name), but saw " + target + extraNote); - break; - } - - // var divertTarget = target as DivertTargetValue; - var divertTarget = target; - // var container = ContentAtPath (divertTarget.targetPath) as Container; - var container = this.ContentAtPath(divertTarget.targetPath); - - var eitherCount; - if (evalCommand.commandType == ControlCommand.CommandType.TurnsSince) eitherCount = this.TurnsSinceForContainer(container);else eitherCount = this.VisitCountForContainer(container); - - this.state.PushEvaluationStack(new IntValue(eitherCount)); - break; - - case ControlCommand.CommandType.Random: - var maxInt = this.state.PopEvaluationStack(); - var minInt = this.state.PopEvaluationStack(); - - if (minInt == null || minInt instanceof IntValue === false) this.Error("Invalid value for minimum parameter of RANDOM(min, max)"); - - if (maxInt == null || minInt instanceof IntValue === false) this.Error("Invalid value for maximum parameter of RANDOM(min, max)"); - - // +1 because it's inclusive of min and max, for e.g. RANDOM(1,6) for a dice roll. - var randomRange = maxInt.value - minInt.value + 1; - if (randomRange <= 0) this.Error("RANDOM was called with minimum as " + minInt.value + " and maximum as " + maxInt.value + ". The maximum must be larger"); - - var resultSeed = this.state.storySeed + this.state.previousRandom; - var random = new PRNG(resultSeed); - - var nextRandom = random.next(); - var chosenValue = nextRandom % randomRange + minInt.value; - this.state.PushEvaluationStack(new IntValue(chosenValue)); - - // Next random number (rather than keeping the Random object around) - this.state.previousRandom = nextRandom; - break; - - case ControlCommand.CommandType.SeedRandom: - var seed = this.state.PopEvaluationStack(); - if (seed == null || seed instanceof IntValue === false) this.Error("Invalid value passed to SEED_RANDOM"); - - // Story seed affects both RANDOM and shuffle behaviour - this.state.storySeed = seed.value; - this.state.previousRandom = 0; - - // SEED_RANDOM returns nothing. - this.state.PushEvaluationStack(new Void()); - break; - - case ControlCommand.CommandType.VisitIndex: - var count = this.VisitCountForContainer(this.state.currentContainer) - 1; // index not count - this.state.PushEvaluationStack(new IntValue(count)); - break; - - case ControlCommand.CommandType.SequenceShuffleIndex: - var shuffleIndex = this.NextSequenceShuffleIndex(); - this.state.PushEvaluationStack(new IntValue(shuffleIndex)); - break; - - case ControlCommand.CommandType.StartThread: - // Handled in main step function - break; - - case ControlCommand.CommandType.Done: - - // We may exist in the context of the initial - // act of creating the thread, or in the context of - // evaluating the content. - if (this.state.callStack.canPopThread) { - this.state.callStack.PopThread(); - } - - // In normal flow - allow safe exit without warning - else { - this.state.didSafeExit = true; - - // Stop flow in current thread - this.state.currentContentObject = null; - } - - break; - - // Force flow to end completely - case ControlCommand.CommandType.End: - this.state.ForceEnd(); - break; - - case ControlCommand.CommandType.ListFromInt: - // var intVal = state.PopEvaluationStack () as IntValue; - var intVal = parseInt(this.state.PopEvaluationStack()); - // var listNameVal = state.PopEvaluationStack () as StringValue; - var listNameVal = this.state.PopEvaluationStack().toString(); - - var generatedListValue = null; - - var foundListDef; - if (foundListDef = this.listDefinitions.TryGetDefinition(listNameVal, foundListDef)) { - var foundItem = foundListDef.TryGetItemWithValue(intVal.value); - if (foundItem.exists) { - generatedListValue = new ListValue(foundItem.item, intVal.value); - } - } else { - throw new StoryException("Failed to find LIST called " + listNameVal.value); - } - - if (generatedListValue == null) generatedListValue = new ListValue(); - - this.state.PushEvaluationStack(generatedListValue); - break; - - case ControlCommand.CommandType.ListRange: - var max = this.state.PopEvaluationStack(); - var min = this.state.PopEvaluationStack(); - - // var targetList = state.PopEvaluationStack () as ListValue; - var targetList = this.state.PopEvaluationStack(); - - if (targetList instanceof ListValue === false || targetList == null || min == null || max == null) throw new StoryException("Expected list, minimum and maximum for LIST_RANGE"); - - // Allow either int or a particular list item to be passed for the bounds, - // so wrap up a function to handle this casting for us. - var IntBound = function IntBound(obj) { - // var listValue = obj as ListValue; - var listValue = obj; - if (listValue instanceof ListValue) { - return parseInt(listValue.value.maxItem.Value); - } - - // var intValue = obj as IntValue; - var intValue = obj; - if (intValue instanceof IntValue) { - return intValue.value; - } - - return -1; - }; - - var minVal = IntBound(min); - var maxVal = IntBound(max); - if (minVal == -1) throw new StoryException("Invalid min range bound passed to LIST_VALUE(): " + min); - - if (maxVal == -1) throw new StoryException("Invalid max range bound passed to LIST_VALUE(): " + max); - - // Extract the range of items from the origin list - var result = new ListValue(); - var origins = targetList.value.origins; - - if (origins != null) { - origins.forEach(function (origin) { - var rangeFromOrigin = origin.ListRange(minVal, maxVal); - rangeFromOrigin.value.forEach(function (kv) { - result.value.Add(kv.Key, kv.Value); - }); - }); - } - - this.state.PushEvaluationStack(result); - break; - - default: - this.Error("unhandled ControlCommand: " + evalCommand); - break; - } - - return true; - } - - // Variable assignment - else if (contentObj instanceof VariableAssignment) { - var varAss = contentObj; - var assignedVal = this.state.PopEvaluationStack(); - - // When in temporary evaluation, don't create new variables purely within - // the temporary context, but attempt to create them globally - //var prioritiseHigherInCallStack = _temporaryEvaluationContainer != null; - - this.state.variablesState.Assign(varAss, assignedVal); - - return true; - } - - // Variable reference - else if (contentObj instanceof VariableReference) { - var varRef = contentObj; - var foundValue = null; - - // Explicit read count value - if (varRef.pathForCount != null) { - - var container = varRef.containerForCount; - var count = this.VisitCountForContainer(container); - foundValue = new IntValue(count); - } - - // Normal variable reference - else { - - foundValue = this.state.variablesState.GetVariableWithName(varRef.name); - - if (foundValue == null) { - this.Error("Uninitialised variable: " + varRef.name); - foundValue = new IntValue(0); - } - } - - this.state.PushEvaluationStack(foundValue); - - return true; - } - - // Native function call - else if (contentObj instanceof NativeFunctionCall) { - var func = contentObj; - var funcParams = this.state.PopEvaluationStack(func.numberOfParameters); - var result = func.Call(funcParams); - this.state.PushEvaluationStack(result); - return true; - } - - // No control content, must be ordinary content - return false; - } - }, { - key: 'ChoosePathString', - value: function ChoosePathString(path, args) { - args = args || []; - this.state.PassArgumentsToEvaluationStack(args); - this.ChoosePath(new Path$1(path)); - } - }, { - key: 'ChoosePath', - value: function ChoosePath(p) { - this.state.SetChosenPath(p); - - // Take a note of newly visited containers for read counts etc - this.VisitChangedContainersDueToDivert(); - } - }, { - key: 'ChooseChoiceIndex', - value: function ChooseChoiceIndex(choiceIdx) { - choiceIdx = choiceIdx; - var choices = this.currentChoices; - if (choiceIdx < 0 || choiceIdx > choices.length) console.warn("choice out of range"); - - // Replace callstack with the one from the thread at the choosing point, - // so that we can jump into the right place in the flow. - // This is important in case the flow was forked by a new thread, which - // can create multiple leading edges for the story, each of - // which has its own context. - var choiceToChoose = choices[choiceIdx]; - this.state.callStack.currentThread = choiceToChoose.threadAtGeneration; - - this.ChoosePath(choiceToChoose.choicePoint.choiceTarget.path); - } - }, { - key: 'HasFunction', - value: function HasFunction(functionName) { - try { - return this.ContentAtPath(new Path$1(functionName)) instanceof Container; - } catch (e) { - return false; - } - } - }, { - key: 'EvaluateFunction', - value: function EvaluateFunction(functionName, args, returnTextOutput) { - //EvaluateFunction behaves slightly differently than the C# version. In C#, you can pass a (second) parameter `out textOutput` to get the text outputted by the function. This is not possible in js. Instead, we maintain the regular signature (functionName, args), plus an optional third parameter returnTextOutput. If set to true, we will return both the textOutput and the returned value, as an object. - returnTextOutput = !!returnTextOutput; - - if (functionName == null) { - throw "Function is null"; - } else if (functionName == '' || functionName.trim() == '') { - throw "Function is empty or white space."; - } - - var funcContainer = null; - try { - funcContainer = this.ContentAtPath(new Path$1(functionName)); - } catch (e) { - if (e.message.indexOf("not found") >= 0) throw "Function doesn't exist: '" + functionName + "'";else throw e; - } - - this.state.StartExternalFunctionEvaluation(funcContainer, args); - - // Evaluate the function, and collect the string output - var stringOutput = new StringBuilder(); - while (this.canContinue) { - stringOutput.Append(this.Continue()); - } - var textOutput = stringOutput.toString(); - - var result = this.state.CompleteExternalFunctionEvaluation(); - - return returnTextOutput ? { 'returned': result, 'output': textOutput } : result; - } - }, { - key: 'EvaluateExpression', - value: function EvaluateExpression(exprContainer) { - var startCallStackHeight = this.state.callStack.elements.length; - - this.state.callStack.Push(PushPopType.Tunnel); - - this._temporaryEvaluationContainer = exprContainer; - - this.state.GoToStart(); - - var evalStackHeight = this.state.evaluationStack.length; - - this.Continue(); - - this._temporaryEvaluationContainer = null; - - // Should have fallen off the end of the Container, which should - // have auto-popped, but just in case we didn't for some reason, - // manually pop to restore the state (including currentPath). - if (this.state.callStack.elements.length > startCallStackHeight) { - this.state.callStack.Pop(); - } - - var endStackHeight = this.state.evaluationStack.length; - if (endStackHeight > evalStackHeight) { - return this.state.PopEvaluationStack(); - } else { - return null; - } - } - }, { - key: 'CallExternalFunction', - value: function CallExternalFunction(funcName, numberOfArguments) { - var func = this._externals[funcName]; - var fallbackFunctionContainer = null; - - var foundExternal = typeof func !== 'undefined'; - - // Try to use fallback function? - if (!foundExternal) { - if (this.allowExternalFunctionFallbacks) { - // fallbackFunctionContainer = ContentAtPath (new Path (funcName)) as Container; - fallbackFunctionContainer = this.ContentAtPath(new Path$1(funcName)); - if (!(fallbackFunctionContainer instanceof Container)) console.warn("Trying to call EXTERNAL function '" + funcName + "' which has not been bound, and fallback ink function could not be found."); - - // Divert direct into fallback function and we're done - this.state.callStack.Push(PushPopType.Function); - this.state.divertedTargetObject = fallbackFunctionContainer; - return; - } else { - console.warn("Trying to call EXTERNAL function '" + funcName + "' which has not been bound (and ink fallbacks disabled)."); - } - } - - // Pop arguments - var args = []; - for (var i = 0; i < numberOfArguments; ++i) { - // var poppedObj = state.PopEvaluationStack () as Value; - var poppedObj = this.state.PopEvaluationStack(); - var valueObj = poppedObj.valueObject; - args.push(valueObj); - } - - // Reverse arguments from the order they were popped, - // so they're the right way round again. - args.reverse(); - - // Run the function! - var funcResult = func(args); - - // Convert return value (if any) to the a type that the ink engine can use - var returnObj = null; - if (funcResult != null) { - returnObj = Value.Create(funcResult); - if (returnObj == null) console.warn("Could not create ink value from returned object of type " + (typeof funcResult === 'undefined' ? 'undefined' : _typeof(funcResult))); - } else { - returnObj = new Void(); - } - - this.state.PushEvaluationStack(returnObj); - } - }, { - key: 'TryCoerce', - value: function TryCoerce(value) { - //we're skipping type coercition in this implementation. First of, js is loosely typed, so it's not that important. Secondly, there is no clean way (AFAIK) for the user to describe what type of parameters he/she expects. - return value; - } - }, { - key: 'BindExternalFunctionGeneral', - value: function BindExternalFunctionGeneral(funcName, func) { - if (this._externals[funcName]) console.warn("Function '" + funcName + "' has already been bound."); - this._externals[funcName] = func; - } - }, { - key: 'BindExternalFunction', - value: function BindExternalFunction(funcName, func) { - var _this2 = this; - - if (!func) console.warn("Can't bind a null function"); - - this.BindExternalFunctionGeneral(funcName, function (args) { - if (args.length < func.length) console.warn("External function expected " + func.length + " arguments"); - - var coercedArgs = []; - for (var i = 0, l = args.length; i < l; i++) { - coercedArgs[i] = _this2.TryCoerce(args[i]); - } - return func.apply(null, coercedArgs); - }); - } - }, { - key: 'UnbindExternalFunction', - value: function UnbindExternalFunction(funcName) { - if (typeof this._externals[funcName] === 'undefined') console.warn("Function '" + funcName + "' has not been bound."); - delete this._externals[funcName]; - } - }, { - key: 'ValidateExternalBindings', - value: function ValidateExternalBindings(containerOrObject, missingExternals) { - var _this3 = this; - - if (!containerOrObject) { - var missingExternals = []; - this.ValidateExternalBindings(this._mainContentContainer, missingExternals); - this._hasValidatedExternals = true; - - // No problem! Validation complete - if (missingExternals.length == 0) { - this._hasValidatedExternals = true; - } - - // Error for all missing externals - else { - var message = "Error: Missing function binding for external"; - message += missingExternals.length > 1 ? "s" : ""; - message += ": '"; - message += missingExternals.join("', '"); - message += "' "; - message += this.allowExternalFunctionFallbacks ? ", and no fallback ink function found." : " (ink fallbacks disabled)"; - - this.Error(message); - } - } else if (containerOrObject instanceof Container) { - var c = containerOrObject; - - c.content.forEach(function (innerContent) { - _this3.ValidateExternalBindings(innerContent, missingExternals); - }); - for (var key in c.namedContent) { - this.ValidateExternalBindings(c.namedContent[key], missingExternals); - } - } else { - var o = containerOrObject; - //the following code is already taken care of above in this implementation - // var container = o as Container; - // if (container) { - // ValidateExternalBindings (container, missingExternals); - // return; - // } - - // var divert = o as Divert; - var divert = o; - if (divert instanceof Divert && divert.isExternal) { - var name = divert.targetPathString; - - if (!this._externals[name]) { - if (this.allowExternalFunctionFallbacks) { - var fallbackFound = !!this.mainContentContainer.namedContent[name]; - if (!fallbackFound) { - missingExternals.push(name); - } - } else { - missingExternals.push(name); - } - } - } - } - } - }, { - key: 'ObserveVariable', - value: function ObserveVariable(variableName, observer) { - if (this._variableObservers == null) this._variableObservers = {}; - - if (this._variableObservers[variableName]) { - this._variableObservers[variableName].push(observer); - } else { - this._variableObservers[variableName] = [observer]; - } - } - }, { - key: 'ObserveVariables', - value: function ObserveVariables(variableNames, observers) { - for (var i = 0, l = variableNames.length; i < l; i++) { - this.ObserveVariable(variableNames[i], observers[i]); - } - } - }, { - key: 'RemoveVariableObserver', - value: function RemoveVariableObserver(observer, specificVariableName) { - if (this._variableObservers == null) return; - - // Remove observer for this specific variable - if (typeof specificVariableName !== 'undefined') { - if (this._variableObservers[specificVariableName]) { - this._variableObservers[specificVariableName].splice(this._variableObservers[specificVariableName].indexOf(observer), 1); - } - } - - // Remove observer for all variables - else { - for (var varName in this._variableObservers) { - this._variableObservers[varName].splice(this._variableObservers[varName].indexOf(observer), 1); - } - } - } - }, { - key: 'VariableStateDidChangeEvent', - value: function VariableStateDidChangeEvent(variableName, newValueObj) { - if (this._variableObservers == null) return; - - var observers = this._variableObservers[variableName]; - if (typeof observers !== 'undefined') { - - if (!(newValueObj instanceof Value)) { - throw "Tried to get the value of a variable that isn't a standard type"; - } - // var val = newValueObj as Value; - var val = newValueObj; - - observers.forEach(function (observer) { - observer(variableName, val.valueObject); - }); - } - } - }, { - key: 'TagsForContentAtPath', - value: function TagsForContentAtPath(path) { - return this.TagsAtStartOfFlowContainerWithPathString(path); - } - }, { - key: 'TagsAtStartOfFlowContainerWithPathString', - value: function TagsAtStartOfFlowContainerWithPathString(pathString) { - var path = new Path$1(pathString); - - // Expected to be global story, knot or stitch - // var flowContainer = ContentAtPath (path) as Container; - var flowContainer = this.ContentAtPath(path); - while (true) { - var firstContent = flowContainer.content[0]; - if (firstContent instanceof Container) flowContainer = firstContent;else break; - } - - // Any initial tag objects count as the "main tags" associated with that story/knot/stitch - var tags = null; - - flowContainer.content.every(function (c) { - // var tag = c as Runtime.Tag; - var tag = c; - if (tag instanceof Tag) { - if (tags == null) tags = []; - tags.push(tag.text); - return true; - } else return false; - }); - - return tags; - } - }, { - key: 'BuildStringOfHierarchy', - value: function BuildStringOfHierarchy() { - var sb = new StringBuilder(); - - this.mainContentContainer.BuildStringOfHierarchy(sb, 0, this.state.currentContentObject); - - return sb.toString(); - } - }, { - key: 'BuildStringOfContainer', - value: function BuildStringOfContainer(container) { - var sb = new StringBuilder(); - container.BuildStringOfHierarchy(sb, 0, this.state.currentContentObject); - return sb.toString(); - } - }, { - key: 'NextContent', - value: function NextContent() { - // Setting previousContentObject is critical for VisitChangedContainersDueToDivert - this.state.previousContentObject = this.state.currentContentObject; - - // Divert step? - if (this.state.divertedTargetObject != null) { - - this.state.currentContentObject = this.state.divertedTargetObject; - this.state.divertedTargetObject = null; - - // Internally uses state.previousContentObject and state.currentContentObject - this.VisitChangedContainersDueToDivert(); - - // Diverted location has valid content? - if (this.state.currentContentObject != null) { - return; - } - - // Otherwise, if diverted location doesn't have valid content, - // drop down and attempt to increment. - // This can happen if the diverted path is intentionally jumping - // to the end of a container - e.g. a Conditional that's re-joining - } - - var successfulPointerIncrement = this.IncrementContentPointer(); - - // Ran out of content? Try to auto-exit from a function, - // or finish evaluating the content of a thread - if (!successfulPointerIncrement) { - - var didPop = false; - - if (this.state.callStack.CanPop(PushPopType.Function)) { - - // Pop from the call stack - this.state.callStack.Pop(PushPopType.Function); - - // This pop was due to dropping off the end of a function that didn't return anything, - // so in this case, we make sure that the evaluator has something to chomp on if it needs it - if (this.state.inExpressionEvaluation) { - this.state.PushEvaluationStack(new Void()); - } - - didPop = true; - } else if (this.state.callStack.canPopThread) { - this.state.callStack.PopThread(); - - didPop = true; - } else { - this.state.TryExitExternalFunctionEvaluation(); - } - - // Step past the point where we last called out - if (didPop && this.state.currentContentObject != null) { - this.NextContent(); - } - } - } - }, { - key: 'IncrementContentPointer', - value: function IncrementContentPointer() { - var successfulIncrement = true; - - var currEl = this.state.callStack.currentElement; - currEl.currentContentIndex++; - - // Each time we step off the end, we fall out to the next container, all the - // while we're in indexed rather than named content - while (currEl.currentContentIndex >= currEl.currentContainer.content.length) { - - successfulIncrement = false; - - // Container nextAncestor = currEl.currentContainer.parent as Container; - var nextAncestor = currEl.currentContainer.parent; - if (nextAncestor instanceof Container === false) { - break; - } - - var indexInAncestor = nextAncestor.content.indexOf(currEl.currentContainer); - if (indexInAncestor == -1) { - break; - } - - currEl.currentContainer = nextAncestor; - currEl.currentContentIndex = indexInAncestor + 1; - - successfulIncrement = true; - } - - if (!successfulIncrement) currEl.currentContainer = null; - - return successfulIncrement; - } - }, { - key: 'TryFollowDefaultInvisibleChoice', - value: function TryFollowDefaultInvisibleChoice() { - var allChoices = this._state.currentChoices; - - // Is a default invisible choice the ONLY choice? - var invisibleChoices = allChoices.filter(function (c) { - return c.choicePoint.isInvisibleDefault; - }); - if (invisibleChoices.length == 0 || allChoices.length > invisibleChoices.length) return false; - - var choice = invisibleChoices[0]; - - this.ChoosePath(choice.choicePoint.choiceTarget.path); - - return true; - } - }, { - key: 'VisitCountForContainer', - value: function VisitCountForContainer(container) { - if (!container.visitsShouldBeCounted) { - console.warn("Read count for target (" + container.name + " - on " + container.debugMetadata + ") unknown. The story may need to be compiled with countAllVisits flag (-c)."); - return 0; - } - - var count = 0; - var containerPathStr = container.path.toString(); - count = this.state.visitCounts[containerPathStr] || count; - return count; - } - }, { - key: 'IncrementVisitCountForContainer', - value: function IncrementVisitCountForContainer(container) { - var count = 0; - var containerPathStr = container.path.toString(); - if (this.state.visitCounts[containerPathStr]) count = this.state.visitCounts[containerPathStr]; - count++; - this.state.visitCounts[containerPathStr] = count; - } - }, { - key: 'RecordTurnIndexVisitToContainer', - value: function RecordTurnIndexVisitToContainer(container) { - var containerPathStr = container.path.toString(); - this.state.turnIndices[containerPathStr] = this.state.currentTurnIndex; - } - }, { - key: 'TurnsSinceForContainer', - value: function TurnsSinceForContainer(container) { - if (!container.turnIndexShouldBeCounted) { - this.Error("TURNS_SINCE() for target (" + container.name + " - on " + container.debugMetadata + ") unknown. The story may need to be compiled with countAllVisits flag (-c)."); - } - - var containerPathStr = container.path.toString(); - var index = this.state.turnIndices[containerPathStr]; - if (typeof index !== 'undefined') { - return this.state.currentTurnIndex - index; - } else { - return -1; - } - } - }, { - key: 'NextSequenceShuffleIndex', - value: function NextSequenceShuffleIndex() { - // var numElementsIntVal = state.PopEvaluationStack () as IntValue; - var numElementsIntVal = this.state.PopEvaluationStack(); - if (!(numElementsIntVal instanceof IntValue)) { - this.Error("expected number of elements in sequence for shuffle index"); - return 0; - } - - var seqContainer = this.state.currentContainer; - - var numElements = numElementsIntVal.value; - - // var seqCountVal = state.PopEvaluationStack () as IntValue; - var seqCountVal = this.state.PopEvaluationStack(); - var seqCount = seqCountVal.value; - var loopIndex = seqCount / numElements; - var iterationIndex = seqCount % numElements; - - // Generate the same shuffle based on: - // - The hash of this container, to make sure it's consistent - // each time the runtime returns to the sequence - // - How many times the runtime has looped around this full shuffle - var seqPathStr = seqContainer.path.toString(); - var sequenceHash = 0; - for (var i = 0, l = seqPathStr.length; i < l; i++) { - sequenceHash += seqPathStr.charCodeAt[i] || 0; - } - var randomSeed = sequenceHash + loopIndex + this.state.storySeed; - var random = new PRNG(parseInt(randomSeed)); - - var unpickedIndices = []; - for (var i = 0; i < numElements; ++i) { - unpickedIndices.push(i); - } - - for (var i = 0; i <= iterationIndex; ++i) { - var chosen = random.next() % unpickedIndices.length; - var chosenIndex = unpickedIndices[chosen]; - unpickedIndices.splice(chosen, 1); - - if (i == iterationIndex) { - return chosenIndex; - } - } - - throw "Should never reach here"; - } - }, { - key: 'Error', - value: function Error(message, useEndLineNumber) { - var e = new StoryException(message); - // e.useEndLineNumber = useEndLineNumber; - throw e; - } - }, { - key: 'AddError', - value: function AddError(message, useEndLineNumber) { - // var dm = this.currentDebugMetadata; - var dm = null; - - if (dm != null) { - var lineNum = useEndLineNumber ? dm.endLineNumber : dm.startLineNumber; - message = "RUNTIME ERROR: '" + dm.fileName + "' line " + lineNum + ": " + message; - } else { - message = "RUNTIME ERROR: " + message; - } - - this.state.AddError(message); - - // In a broken state don't need to know about any other errors. - this.state.ForceEnd(); - } - }, { - key: 'currentChoices', - get: function get$$1() { - // Don't include invisible choices for external usage. - var choices = []; - - this._state.currentChoices.forEach(function (c) { - if (!c.choicePoint.isInvisibleDefault) { - c.index = choices.length; - choices.push(c); - } - }); - - return choices; - } - }, { - key: 'currentText', - get: function get$$1() { - return this.state.currentText; - } - }, { - key: 'currentTags', - get: function get$$1() { - return this.state.currentTags; - } - }, { - key: 'currentErrors', - get: function get$$1() { - return this.state.currentErrors; - } - }, { - key: 'hasError', - get: function get$$1() { - return this.state.hasError; - } - }, { - key: 'variablesState', - get: function get$$1() { - return this.state.variablesState; - } - }, { - key: 'listDefinitions', - get: function get$$1() { - return this._listDefinitions; - } - }, { - key: 'state', - get: function get$$1() { - return this._state; - } - }, { - key: 'mainContentContainer', - get: function get$$1() { - if (this._temporaryEvaluationContainer) { - return this._temporaryEvaluationContainer; - } else { - return this._mainContentContainer; - } - } - }, { - key: 'canContinue', - get: function get$$1() { - return this.state.canContinue; - } - }, { - key: 'globalTags', - get: function get$$1() { - return this.TagsAtStartOfFlowContainerWithPathString(""); - } - }]); - return Story; - }(Object$1); - - exports.Story = Story; - - Object.defineProperty(exports, '__esModule', { value: true }); - -}))); diff --git a/docs/main.js b/docs/main.js index 665706d..bb88abb 100644 --- a/docs/main.js +++ b/docs/main.js @@ -36,7 +36,11 @@ // Get ink to generate the next paragraph var paragraphText = story.Continue(); + if(story.currentTags.length > 0){ + debugger + } + // Create paragraph element var paragraphElement = document.createElement('p'); paragraphElement.innerHTML = paragraphText; diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..d30f40e --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,21 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +/node_modules + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..479d632 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,2444 @@ +This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). + +Below you will find some information on how to perform common tasks.
+You can find the most recent version of this guide [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md). + +## Table of Contents + +- [Updating to New Releases](#updating-to-new-releases) +- [Sending Feedback](#sending-feedback) +- [Folder Structure](#folder-structure) +- [Available Scripts](#available-scripts) + - [npm start](#npm-start) + - [npm test](#npm-test) + - [npm run build](#npm-run-build) + - [npm run eject](#npm-run-eject) +- [Supported Browsers](#supported-browsers) +- [Supported Language Features and Polyfills](#supported-language-features-and-polyfills) +- [Syntax Highlighting in the Editor](#syntax-highlighting-in-the-editor) +- [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor) +- [Debugging in the Editor](#debugging-in-the-editor) +- [Formatting Code Automatically](#formatting-code-automatically) +- [Changing the Page ``](#changing-the-page-title) +- [Installing a Dependency](#installing-a-dependency) +- [Importing a Component](#importing-a-component) +- [Code Splitting](#code-splitting) +- [Adding a Stylesheet](#adding-a-stylesheet) +- [Post-Processing CSS](#post-processing-css) +- [Adding a CSS Preprocessor (Sass, Less etc.)](#adding-a-css-preprocessor-sass-less-etc) +- [Adding Images, Fonts, and Files](#adding-images-fonts-and-files) +- [Using the `public` Folder](#using-the-public-folder) + - [Changing the HTML](#changing-the-html) + - [Adding Assets Outside of the Module System](#adding-assets-outside-of-the-module-system) + - [When to Use the `public` Folder](#when-to-use-the-public-folder) +- [Using Global Variables](#using-global-variables) +- [Adding Bootstrap](#adding-bootstrap) + - [Using a Custom Theme](#using-a-custom-theme) +- [Adding Flow](#adding-flow) +- [Adding a Router](#adding-a-router) +- [Adding Custom Environment Variables](#adding-custom-environment-variables) + - [Referencing Environment Variables in the HTML](#referencing-environment-variables-in-the-html) + - [Adding Temporary Environment Variables In Your Shell](#adding-temporary-environment-variables-in-your-shell) + - [Adding Development Environment Variables In `.env`](#adding-development-environment-variables-in-env) +- [Can I Use Decorators?](#can-i-use-decorators) +- [Fetching Data with AJAX Requests](#fetching-data-with-ajax-requests) +- [Integrating with an API Backend](#integrating-with-an-api-backend) + - [Node](#node) + - [Ruby on Rails](#ruby-on-rails) +- [Proxying API Requests in Development](#proxying-api-requests-in-development) + - ["Invalid Host Header" Errors After Configuring Proxy](#invalid-host-header-errors-after-configuring-proxy) + - [Configuring the Proxy Manually](#configuring-the-proxy-manually) + - [Configuring a WebSocket Proxy](#configuring-a-websocket-proxy) +- [Using HTTPS in Development](#using-https-in-development) +- [Generating Dynamic `<meta>` Tags on the Server](#generating-dynamic-meta-tags-on-the-server) +- [Pre-Rendering into Static HTML Files](#pre-rendering-into-static-html-files) +- [Injecting Data from the Server into the Page](#injecting-data-from-the-server-into-the-page) +- [Running Tests](#running-tests) + - [Filename Conventions](#filename-conventions) + - [Command Line Interface](#command-line-interface) + - [Version Control Integration](#version-control-integration) + - [Writing Tests](#writing-tests) + - [Testing Components](#testing-components) + - [Using Third Party Assertion Libraries](#using-third-party-assertion-libraries) + - [Initializing Test Environment](#initializing-test-environment) + - [Focusing and Excluding Tests](#focusing-and-excluding-tests) + - [Coverage Reporting](#coverage-reporting) + - [Continuous Integration](#continuous-integration) + - [Disabling jsdom](#disabling-jsdom) + - [Snapshot Testing](#snapshot-testing) + - [Editor Integration](#editor-integration) +- [Debugging Tests](#debugging-tests) + - [Debugging Tests in Chrome](#debugging-tests-in-chrome) + - [Debugging Tests in Visual Studio Code](#debugging-tests-in-visual-studio-code) +- [Developing Components in Isolation](#developing-components-in-isolation) + - [Getting Started with Storybook](#getting-started-with-storybook) + - [Getting Started with Styleguidist](#getting-started-with-styleguidist) +- [Publishing Components to npm](#publishing-components-to-npm) +- [Making a Progressive Web App](#making-a-progressive-web-app) + - [Opting Out of Caching](#opting-out-of-caching) + - [Offline-First Considerations](#offline-first-considerations) + - [Progressive Web App Metadata](#progressive-web-app-metadata) +- [Analyzing the Bundle Size](#analyzing-the-bundle-size) +- [Deployment](#deployment) + - [Static Server](#static-server) + - [Other Solutions](#other-solutions) + - [Serving Apps with Client-Side Routing](#serving-apps-with-client-side-routing) + - [Building for Relative Paths](#building-for-relative-paths) + - [Azure](#azure) + - [Firebase](#firebase) + - [GitHub Pages](#github-pages) + - [Heroku](#heroku) + - [Netlify](#netlify) + - [Now](#now) + - [S3 and CloudFront](#s3-and-cloudfront) + - [Surge](#surge) +- [Advanced Configuration](#advanced-configuration) +- [Troubleshooting](#troubleshooting) + - [`npm start` doesn’t detect changes](#npm-start-doesnt-detect-changes) + - [`npm test` hangs on macOS Sierra](#npm-test-hangs-on-macos-sierra) + - [`npm run build` exits too early](#npm-run-build-exits-too-early) + - [`npm run build` fails on Heroku](#npm-run-build-fails-on-heroku) + - [`npm run build` fails to minify](#npm-run-build-fails-to-minify) + - [Moment.js locales are missing](#momentjs-locales-are-missing) +- [Alternatives to Ejecting](#alternatives-to-ejecting) +- [Something Missing?](#something-missing) + +## Updating to New Releases + +Create React App is divided into two packages: + +* `create-react-app` is a global command-line utility that you use to create new projects. +* `react-scripts` is a development dependency in the generated projects (including this one). + +You almost never need to update `create-react-app` itself: it delegates all the setup to `react-scripts`. + +When you run `create-react-app`, it always creates the project with the latest version of `react-scripts` so you’ll get all the new features and improvements in newly created apps automatically. + +To update an existing project to a new version of `react-scripts`, [open the changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md), find the version you’re currently on (check `package.json` in this folder if you’re not sure), and apply the migration instructions for the newer versions. + +In most cases bumping the `react-scripts` version in `package.json` and running `npm install` in this folder should be enough, but it’s good to consult the [changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md) for potential breaking changes. + +We commit to keeping the breaking changes minimal so you can upgrade `react-scripts` painlessly. + +## Sending Feedback + +We are always open to [your feedback](https://github.com/facebookincubator/create-react-app/issues). + +## Folder Structure + +After creation, your project should look like this: + +``` +my-app/ + README.md + node_modules/ + package.json + public/ + index.html + favicon.ico + src/ + App.css + App.js + App.test.js + index.css + index.js + logo.svg +``` + +For the project to build, **these files must exist with exact filenames**: + +* `public/index.html` is the page template; +* `src/index.js` is the JavaScript entry point. + +You can delete or rename the other files. + +You may create subdirectories inside `src`. For faster rebuilds, only files inside `src` are processed by Webpack.<br> +You need to **put any JS and CSS files inside `src`**, otherwise Webpack won’t see them. + +Only files inside `public` can be used from `public/index.html`.<br> +Read instructions below for using assets from JavaScript and HTML. + +You can, however, create more top-level directories.<br> +They will not be included in the production build so you can use them for things like documentation. + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.<br> +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.<br> +You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.<br> +See the section about [running tests](#running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.<br> +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.<br> +Your app is ready to be deployed! + +See the section about [deployment](#deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Supported Browsers + +By default, the generated project uses the latest version of React. + +You can refer [to the React documentation](https://reactjs.org/docs/react-dom.html#browser-support) for more information about supported browsers. + +## Supported Language Features and Polyfills + +This project supports a superset of the latest JavaScript standard.<br> +In addition to [ES6](https://github.com/lukehoban/es6features) syntax features, it also supports: + +* [Exponentiation Operator](https://github.com/rwaldron/exponentiation-operator) (ES2016). +* [Async/await](https://github.com/tc39/ecmascript-asyncawait) (ES2017). +* [Object Rest/Spread Properties](https://github.com/sebmarkbage/ecmascript-rest-spread) (stage 3 proposal). +* [Dynamic import()](https://github.com/tc39/proposal-dynamic-import) (stage 3 proposal) +* [Class Fields and Static Properties](https://github.com/tc39/proposal-class-public-fields) (part of stage 3 proposal). +* [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) and [Flow](https://flowtype.org/) syntax. + +Learn more about [different proposal stages](https://babeljs.io/docs/plugins/#presets-stage-x-experimental-presets-). + +While we recommend using experimental proposals with some caution, Facebook heavily uses these features in the product code, so we intend to provide [codemods](https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb) if any of these proposals change in the future. + +Note that **the project only includes a few ES6 [polyfills](https://en.wikipedia.org/wiki/Polyfill)**: + +* [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) via [`object-assign`](https://github.com/sindresorhus/object-assign). +* [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) via [`promise`](https://github.com/then/promise). +* [`fetch()`](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) via [`whatwg-fetch`](https://github.com/github/fetch). + +If you use any other ES6+ features that need **runtime support** (such as `Array.from()` or `Symbol`), make sure you are including the appropriate polyfills manually, or that the browsers you are targeting already support them. + +Also note that using some newer syntax features like `for...of` or `[...nonArrayValue]` causes Babel to emit code that depends on ES6 runtime features and might not work without a polyfill. When in doubt, use [Babel REPL](https://babeljs.io/repl/) to see what any specific syntax compiles down to. + +## Syntax Highlighting in the Editor + +To configure the syntax highlighting in your favorite text editor, head to the [relevant Babel documentation page](https://babeljs.io/docs/editors) and follow the instructions. Some of the most popular editors are covered. + +## Displaying Lint Output in the Editor + +>Note: this feature is available with `react-scripts@0.2.0` and higher.<br> +>It also only works with npm 3 or higher. + +Some editors, including Sublime Text, Atom, and Visual Studio Code, provide plugins for ESLint. + +They are not required for linting. You should see the linter output right in your terminal as well as the browser console. However, if you prefer the lint results to appear right in your editor, there are some extra steps you can do. + +You would need to install an ESLint plugin for your editor first. Then, add a file called `.eslintrc` to the project root: + +```js +{ + "extends": "react-app" +} +``` + +Now your editor should report the linting warnings. + +Note that even if you edit your `.eslintrc` file further, these changes will **only affect the editor integration**. They won’t affect the terminal and in-browser lint output. This is because Create React App intentionally provides a minimal set of rules that find common mistakes. + +If you want to enforce a coding style for your project, consider using [Prettier](https://github.com/jlongster/prettier) instead of ESLint style rules. + +## Debugging in the Editor + +**This feature is currently only supported by [Visual Studio Code](https://code.visualstudio.com) and [WebStorm](https://www.jetbrains.com/webstorm/).** + +Visual Studio Code and WebStorm support debugging out of the box with Create React App. This enables you as a developer to write and debug your React code without leaving the editor, and most importantly it enables you to have a continuous development workflow, where context switching is minimal, as you don’t have to switch between tools. + +### Visual Studio Code + +You would need to have the latest version of [VS Code](https://code.visualstudio.com) and VS Code [Chrome Debugger Extension](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) installed. + +Then add the block below to your `launch.json` file and put it inside the `.vscode` folder in your app’s root directory. + +```json +{ + "version": "0.2.0", + "configurations": [{ + "name": "Chrome", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000", + "webRoot": "${workspaceRoot}/src", + "sourceMapPathOverrides": { + "webpack:///src/*": "${webRoot}/*" + } + }] +} +``` +>Note: the URL may be different if you've made adjustments via the [HOST or PORT environment variables](#advanced-configuration). + +Start your app by running `npm start`, and start debugging in VS Code by pressing `F5` or by clicking the green debug icon. You can now write code, set breakpoints, make changes to the code, and debug your newly modified code—all from your editor. + +Having problems with VS Code Debugging? Please see their [troubleshooting guide](https://github.com/Microsoft/vscode-chrome-debug/blob/master/README.md#troubleshooting). + +### WebStorm + +You would need to have [WebStorm](https://www.jetbrains.com/webstorm/) and [JetBrains IDE Support](https://chrome.google.com/webstore/detail/jetbrains-ide-support/hmhgeddbohgjknpmjagkdomcpobmllji) Chrome extension installed. + +In the WebStorm menu `Run` select `Edit Configurations...`. Then click `+` and select `JavaScript Debug`. Paste `http://localhost:3000` into the URL field and save the configuration. + +>Note: the URL may be different if you've made adjustments via the [HOST or PORT environment variables](#advanced-configuration). + +Start your app by running `npm start`, then press `^D` on macOS or `F9` on Windows and Linux or click the green debug icon to start debugging in WebStorm. + +The same way you can debug your application in IntelliJ IDEA Ultimate, PhpStorm, PyCharm Pro, and RubyMine. + +## Formatting Code Automatically + +Prettier is an opinionated code formatter with support for JavaScript, CSS and JSON. With Prettier you can format the code you write automatically to ensure a code style within your project. See the [Prettier's GitHub page](https://github.com/prettier/prettier) for more information, and look at this [page to see it in action](https://prettier.github.io/prettier/). + +To format our code whenever we make a commit in git, we need to install the following dependencies: + +```sh +npm install --save husky lint-staged prettier +``` + +Alternatively you may use `yarn`: + +```sh +yarn add husky lint-staged prettier +``` + +* `husky` makes it easy to use githooks as if they are npm scripts. +* `lint-staged` allows us to run scripts on staged files in git. See this [blog post about lint-staged to learn more about it](https://medium.com/@okonetchnikov/make-linting-great-again-f3890e1ad6b8). +* `prettier` is the JavaScript formatter we will run before commits. + +Now we can make sure every file is formatted correctly by adding a few lines to the `package.json` in the project root. + +Add the following line to `scripts` section: + +```diff + "scripts": { ++ "precommit": "lint-staged", + "start": "react-scripts start", + "build": "react-scripts build", +``` + +Next we add a 'lint-staged' field to the `package.json`, for example: + +```diff + "dependencies": { + // ... + }, ++ "lint-staged": { ++ "src/**/*.{js,jsx,json,css}": [ ++ "prettier --single-quote --write", ++ "git add" ++ ] ++ }, + "scripts": { +``` + +Now, whenever you make a commit, Prettier will format the changed files automatically. You can also run `./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,json,css}"` to format your entire project for the first time. + +Next you might want to integrate Prettier in your favorite editor. Read the section on [Editor Integration](https://prettier.io/docs/en/editors.html) on the Prettier GitHub page. + +## Changing the Page `<title>` + +You can find the source HTML file in the `public` folder of the generated project. You may edit the `<title>` tag in it to change the title from “React App” to anything else. + +Note that normally you wouldn’t edit files in the `public` folder very often. For example, [adding a stylesheet](#adding-a-stylesheet) is done without touching the HTML. + +If you need to dynamically update the page title based on the content, you can use the browser [`document.title`](https://developer.mozilla.org/en-US/docs/Web/API/Document/title) API. For more complex scenarios when you want to change the title from React components, you can use [React Helmet](https://github.com/nfl/react-helmet), a third party library. + +If you use a custom server for your app in production and want to modify the title before it gets sent to the browser, you can follow advice in [this section](#generating-dynamic-meta-tags-on-the-server). Alternatively, you can pre-build each page as a static HTML file which then loads the JavaScript bundle, which is covered [here](#pre-rendering-into-static-html-files). + +## Installing a Dependency + +The generated project includes React and ReactDOM as dependencies. It also includes a set of scripts used by Create React App as a development dependency. You may install other dependencies (for example, React Router) with `npm`: + +```sh +npm install --save react-router +``` + +Alternatively you may use `yarn`: + +```sh +yarn add react-router +``` + +This works for any library, not just `react-router`. + +## Importing a Component + +This project setup supports ES6 modules thanks to Babel.<br> +While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](http://exploringjs.com/es6/ch_modules.html) instead. + +For example: + +### `Button.js` + +```js +import React, { Component } from 'react'; + +class Button extends Component { + render() { + // ... + } +} + +export default Button; // Don’t forget to use export default! +``` + +### `DangerButton.js` + + +```js +import React, { Component } from 'react'; +import Button from './Button'; // Import a component from another file + +class DangerButton extends Component { + render() { + return <Button color="red" />; + } +} + +export default DangerButton; +``` + +Be aware of the [difference between default and named exports](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281). It is a common source of mistakes. + +We suggest that you stick to using default imports and exports when a module only exports a single thing (for example, a component). That’s what you get when you use `export default Button` and `import Button from './Button'`. + +Named exports are useful for utility modules that export several functions. A module may have at most one default export and as many named exports as you like. + +Learn more about ES6 modules: + +* [When to use the curly braces?](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281) +* [Exploring ES6: Modules](http://exploringjs.com/es6/ch_modules.html) +* [Understanding ES6: Modules](https://leanpub.com/understandinges6/read#leanpub-auto-encapsulating-code-with-modules) + +## Code Splitting + +Instead of downloading the entire app before users can use it, code splitting allows you to split your code into small chunks which you can then load on demand. + +This project setup supports code splitting via [dynamic `import()`](http://2ality.com/2017/01/import-operator.html#loading-code-on-demand). Its [proposal](https://github.com/tc39/proposal-dynamic-import) is in stage 3. The `import()` function-like form takes the module name as an argument and returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which always resolves to the namespace object of the module. + +Here is an example: + +### `moduleA.js` + +```js +const moduleA = 'Hello'; + +export { moduleA }; +``` +### `App.js` + +```js +import React, { Component } from 'react'; + +class App extends Component { + handleClick = () => { + import('./moduleA') + .then(({ moduleA }) => { + // Use moduleA + }) + .catch(err => { + // Handle failure + }); + }; + + render() { + return ( + <div> + <button onClick={this.handleClick}>Load</button> + </div> + ); + } +} + +export default App; +``` + +This will make `moduleA.js` and all its unique dependencies as a separate chunk that only loads after the user clicks the 'Load' button. + +You can also use it with `async` / `await` syntax if you prefer it. + +### With React Router + +If you are using React Router check out [this tutorial](http://serverless-stack.com/chapters/code-splitting-in-create-react-app.html) on how to use code splitting with it. You can find the companion GitHub repository [here](https://github.com/AnomalyInnovations/serverless-stack-demo-client/tree/code-splitting-in-create-react-app). + +Also check out the [Code Splitting](https://reactjs.org/docs/code-splitting.html) section in React documentation. + +## Adding a Stylesheet + +This project setup uses [Webpack](https://webpack.js.org/) for handling all assets. Webpack offers a custom way of “extending” the concept of `import` beyond JavaScript. To express that a JavaScript file depends on a CSS file, you need to **import the CSS from the JavaScript file**: + +### `Button.css` + +```css +.Button { + padding: 20px; +} +``` + +### `Button.js` + +```js +import React, { Component } from 'react'; +import './Button.css'; // Tell Webpack that Button.js uses these styles + +class Button extends Component { + render() { + // You can use them as regular CSS styles + return <div className="Button" />; + } +} +``` + +**This is not required for React** but many people find this feature convenient. You can read about the benefits of this approach [here](https://medium.com/seek-ui-engineering/block-element-modifying-your-javascript-components-d7f99fcab52b). However you should be aware that this makes your code less portable to other build tools and environments than Webpack. + +In development, expressing dependencies this way allows your styles to be reloaded on the fly as you edit them. In production, all CSS files will be concatenated into a single minified `.css` file in the build output. + +If you are concerned about using Webpack-specific semantics, you can put all your CSS right into `src/index.css`. It would still be imported from `src/index.js`, but you could always remove that import if you later migrate to a different build tool. + +## Post-Processing CSS + +This project setup minifies your CSS and adds vendor prefixes to it automatically through [Autoprefixer](https://github.com/postcss/autoprefixer) so you don’t need to worry about it. + +For example, this: + +```css +.App { + display: flex; + flex-direction: row; + align-items: center; +} +``` + +becomes this: + +```css +.App { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +``` + +If you need to disable autoprefixing for some reason, [follow this section](https://github.com/postcss/autoprefixer#disabling). + +## Adding a CSS Preprocessor (Sass, Less etc.) + +Generally, we recommend that you don’t reuse the same CSS classes across different components. For example, instead of using a `.Button` CSS class in `<AcceptButton>` and `<RejectButton>` components, we recommend creating a `<Button>` component with its own `.Button` styles, that both `<AcceptButton>` and `<RejectButton>` can render (but [not inherit](https://facebook.github.io/react/docs/composition-vs-inheritance.html)). + +Following this rule often makes CSS preprocessors less useful, as features like mixins and nesting are replaced by component composition. You can, however, integrate a CSS preprocessor if you find it valuable. In this walkthrough, we will be using Sass, but you can also use Less, or another alternative. + +First, let’s install the command-line interface for Sass: + +```sh +npm install --save node-sass-chokidar +``` + +Alternatively you may use `yarn`: + +```sh +yarn add node-sass-chokidar +``` + +Then in `package.json`, add the following lines to `scripts`: + +```diff + "scripts": { ++ "build-css": "node-sass-chokidar src/ -o src/", ++ "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive", + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", +``` + +>Note: To use a different preprocessor, replace `build-css` and `watch-css` commands according to your preprocessor’s documentation. + +Now you can rename `src/App.css` to `src/App.scss` and run `npm run watch-css`. The watcher will find every Sass file in `src` subdirectories, and create a corresponding CSS file next to it, in our case overwriting `src/App.css`. Since `src/App.js` still imports `src/App.css`, the styles become a part of your application. You can now edit `src/App.scss`, and `src/App.css` will be regenerated. + +To share variables between Sass files, you can use Sass imports. For example, `src/App.scss` and other component style files could include `@import "./shared.scss";` with variable definitions. + +To enable importing files without using relative paths, you can add the `--include-path` option to the command in `package.json`. + +``` +"build-css": "node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/", +"watch-css": "npm run build-css && node-sass-chokidar --include-path ./src --include-path ./node_modules src/ -o src/ --watch --recursive", +``` + +This will allow you to do imports like + +```scss +@import 'styles/_colors.scss'; // assuming a styles directory under src/ +@import 'nprogress/nprogress'; // importing a css file from the nprogress node module +``` + +At this point you might want to remove all CSS files from the source control, and add `src/**/*.css` to your `.gitignore` file. It is generally a good practice to keep the build products outside of the source control. + +As a final step, you may find it convenient to run `watch-css` automatically with `npm start`, and run `build-css` as a part of `npm run build`. You can use the `&&` operator to execute two scripts sequentially. However, there is no cross-platform way to run two scripts in parallel, so we will install a package for this: + +```sh +npm install --save npm-run-all +``` + +Alternatively you may use `yarn`: + +```sh +yarn add npm-run-all +``` + +Then we can change `start` and `build` scripts to include the CSS preprocessor commands: + +```diff + "scripts": { + "build-css": "node-sass-chokidar src/ -o src/", + "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive", +- "start": "react-scripts start", +- "build": "react-scripts build", ++ "start-js": "react-scripts start", ++ "start": "npm-run-all -p watch-css start-js", ++ "build-js": "react-scripts build", ++ "build": "npm-run-all build-css build-js", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +``` + +Now running `npm start` and `npm run build` also builds Sass files. + +**Why `node-sass-chokidar`?** + +`node-sass` has been reported as having the following issues: + +- `node-sass --watch` has been reported to have *performance issues* in certain conditions when used in a virtual machine or with docker. + +- Infinite styles compiling [#1939](https://github.com/facebookincubator/create-react-app/issues/1939) + +- `node-sass` has been reported as having issues with detecting new files in a directory [#1891](https://github.com/sass/node-sass/issues/1891) + + `node-sass-chokidar` is used here as it addresses these issues. + +## Adding Images, Fonts, and Files + +With Webpack, using static assets like images and fonts works similarly to CSS. + +You can **`import` a file right in a JavaScript module**. This tells Webpack to include that file in the bundle. Unlike CSS imports, importing a file gives you a string value. This value is the final path you can reference in your code, e.g. as the `src` attribute of an image or the `href` of a link to a PDF. + +To reduce the number of requests to the server, importing images that are less than 10,000 bytes returns a [data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) instead of a path. This applies to the following file extensions: bmp, gif, jpg, jpeg, and png. SVG files are excluded due to [#1153](https://github.com/facebookincubator/create-react-app/issues/1153). + +Here is an example: + +```js +import React from 'react'; +import logo from './logo.png'; // Tell Webpack this JS file uses this image + +console.log(logo); // /logo.84287d09.png + +function Header() { + // Import result is the URL of your image + return <img src={logo} alt="Logo" />; +} + +export default Header; +``` + +This ensures that when the project is built, Webpack will correctly move the images into the build folder, and provide us with correct paths. + +This works in CSS too: + +```css +.Logo { + background-image: url(./logo.png); +} +``` + +Webpack finds all relative module references in CSS (they start with `./`) and replaces them with the final paths from the compiled bundle. If you make a typo or accidentally delete an important file, you will see a compilation error, just like when you import a non-existent JavaScript module. The final filenames in the compiled bundle are generated by Webpack from content hashes. If the file content changes in the future, Webpack will give it a different name in production so you don’t need to worry about long-term caching of assets. + +Please be advised that this is also a custom feature of Webpack. + +**It is not required for React** but many people enjoy it (and React Native uses a similar mechanism for images).<br> +An alternative way of handling static assets is described in the next section. + +## Using the `public` Folder + +>Note: this feature is available with `react-scripts@0.5.0` and higher. + +### Changing the HTML + +The `public` folder contains the HTML file so you can tweak it, for example, to [set the page title](#changing-the-page-title). +The `<script>` tag with the compiled code will be added to it automatically during the build process. + +### Adding Assets Outside of the Module System + +You can also add other assets to the `public` folder. + +Note that we normally encourage you to `import` assets in JavaScript files instead. +For example, see the sections on [adding a stylesheet](#adding-a-stylesheet) and [adding images and fonts](#adding-images-fonts-and-files). +This mechanism provides a number of benefits: + +* Scripts and stylesheets get minified and bundled together to avoid extra network requests. +* Missing files cause compilation errors instead of 404 errors for your users. +* Result filenames include content hashes so you don’t need to worry about browsers caching their old versions. + +However there is an **escape hatch** that you can use to add an asset outside of the module system. + +If you put a file into the `public` folder, it will **not** be processed by Webpack. Instead it will be copied into the build folder untouched. To reference assets in the `public` folder, you need to use a special variable called `PUBLIC_URL`. + +Inside `index.html`, you can use it like this: + +```html +<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> +``` + +Only files inside the `public` folder will be accessible by `%PUBLIC_URL%` prefix. If you need to use a file from `src` or `node_modules`, you’ll have to copy it there to explicitly specify your intention to make this file a part of the build. + +When you run `npm run build`, Create React App will substitute `%PUBLIC_URL%` with a correct absolute path so your project works even if you use client-side routing or host it at a non-root URL. + +In JavaScript code, you can use `process.env.PUBLIC_URL` for similar purposes: + +```js +render() { + // Note: this is an escape hatch and should be used sparingly! + // Normally we recommend using `import` for getting asset URLs + // as described in “Adding Images and Fonts” above this section. + return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; +} +``` + +Keep in mind the downsides of this approach: + +* None of the files in `public` folder get post-processed or minified. +* Missing files will not be called at compilation time, and will cause 404 errors for your users. +* Result filenames won’t include content hashes so you’ll need to add query arguments or rename them every time they change. + +### When to Use the `public` Folder + +Normally we recommend importing [stylesheets](#adding-a-stylesheet), [images, and fonts](#adding-images-fonts-and-files) from JavaScript. +The `public` folder is useful as a workaround for a number of less common cases: + +* You need a file with a specific name in the build output, such as [`manifest.webmanifest`](https://developer.mozilla.org/en-US/docs/Web/Manifest). +* You have thousands of images and need to dynamically reference their paths. +* You want to include a small script like [`pace.js`](http://github.hubspot.com/pace/docs/welcome/) outside of the bundled code. +* Some library may be incompatible with Webpack and you have no other option but to include it as a `<script>` tag. + +Note that if you add a `<script>` that declares global variables, you also need to read the next section on using them. + +## Using Global Variables + +When you include a script in the HTML file that defines global variables and try to use one of these variables in the code, the linter will complain because it cannot see the definition of the variable. + +You can avoid this by reading the global variable explicitly from the `window` object, for example: + +```js +const $ = window.$; +``` + +This makes it obvious you are using a global variable intentionally rather than because of a typo. + +Alternatively, you can force the linter to ignore any line by adding `// eslint-disable-line` after it. + +## Adding Bootstrap + +You don’t have to use [React Bootstrap](https://react-bootstrap.github.io) together with React but it is a popular library for integrating Bootstrap with React apps. If you need it, you can integrate it with Create React App by following these steps: + +Install React Bootstrap and Bootstrap from npm. React Bootstrap does not include Bootstrap CSS so this needs to be installed as well: + +```sh +npm install --save react-bootstrap bootstrap@3 +``` + +Alternatively you may use `yarn`: + +```sh +yarn add react-bootstrap bootstrap@3 +``` + +Import Bootstrap CSS and optionally Bootstrap theme CSS in the beginning of your ```src/index.js``` file: + +```js +import 'bootstrap/dist/css/bootstrap.css'; +import 'bootstrap/dist/css/bootstrap-theme.css'; +// Put any other imports below so that CSS from your +// components takes precedence over default styles. +``` + +Import required React Bootstrap components within ```src/App.js``` file or your custom component files: + +```js +import { Navbar, Jumbotron, Button } from 'react-bootstrap'; +``` + +Now you are ready to use the imported React Bootstrap components within your component hierarchy defined in the render method. Here is an example [`App.js`](https://gist.githubusercontent.com/gaearon/85d8c067f6af1e56277c82d19fd4da7b/raw/6158dd991b67284e9fc8d70b9d973efe87659d72/App.js) redone using React Bootstrap. + +### Using a Custom Theme + +Sometimes you might need to tweak the visual styles of Bootstrap (or equivalent package).<br> +We suggest the following approach: + +* Create a new package that depends on the package you wish to customize, e.g. Bootstrap. +* Add the necessary build steps to tweak the theme, and publish your package on npm. +* Install your own theme npm package as a dependency of your app. + +Here is an example of adding a [customized Bootstrap](https://medium.com/@tacomanator/customizing-create-react-app-aa9ffb88165) that follows these steps. + +## Adding Flow + +Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept. + +Recent versions of [Flow](http://flowtype.org/) work with Create React App projects out of the box. + +To add Flow to a Create React App project, follow these steps: + +1. Run `npm install --save flow-bin` (or `yarn add flow-bin`). +2. Add `"flow": "flow"` to the `scripts` section of your `package.json`. +3. Run `npm run flow init` (or `yarn flow init`) to create a [`.flowconfig` file](https://flowtype.org/docs/advanced-configuration.html) in the root directory. +4. Add `// @flow` to any files you want to type check (for example, to `src/App.js`). + +Now you can run `npm run flow` (or `yarn flow`) to check the files for type errors. +You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience. +In the future we plan to integrate it into Create React App even more closely. + +To learn more about Flow, check out [its documentation](https://flowtype.org/). + +## Adding a Router + +Create React App doesn't prescribe a specific routing solution, but [React Router](https://reacttraining.com/react-router/) is the most popular one. + +To add it, run: + +```sh +npm install --save react-router-dom +``` + +Alternatively you may use `yarn`: + +```sh +yarn add react-router-dom +``` + +To try it, delete all the code in `src/App.js` and replace it with any of the examples on its website. The [Basic Example](https://reacttraining.com/react-router/web/example/basic) is a good place to get started. + +Note that [you may need to configure your production server to support client-side routing](#serving-apps-with-client-side-routing) before deploying your app. + +## Adding Custom Environment Variables + +>Note: this feature is available with `react-scripts@0.2.3` and higher. + +Your project can consume variables declared in your environment as if they were declared locally in your JS files. By +default you will have `NODE_ENV` defined for you, and any other environment variables starting with +`REACT_APP_`. + +**The environment variables are embedded during the build time**. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime. To read them at runtime, you would need to load HTML into memory on the server and replace placeholders in runtime, just like [described here](#injecting-data-from-the-server-into-the-page). Alternatively you can rebuild the app on the server anytime you change them. + +>Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid accidentally [exposing a private key on the machine that could have the same name](https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. + +These environment variables will be defined for you on `process.env`. For example, having an environment +variable named `REACT_APP_SECRET_CODE` will be exposed in your JS as `process.env.REACT_APP_SECRET_CODE`. + +There is also a special built-in environment variable called `NODE_ENV`. You can read it from `process.env.NODE_ENV`. When you run `npm start`, it is always equal to `'development'`, when you run `npm test` it is always equal to `'test'`, and when you run `npm run build` to make a production bundle, it is always equal to `'production'`. **You cannot override `NODE_ENV` manually.** This prevents developers from accidentally deploying a slow development build to production. + +These environment variables can be useful for displaying information conditionally based on where the project is +deployed or consuming sensitive data that lives outside of version control. + +First, you need to have environment variables defined. For example, let’s say you wanted to consume a secret defined +in the environment inside a `<form>`: + +```jsx +render() { + return ( + <div> + <small>You are running this application in <b>{process.env.NODE_ENV}</b> mode.</small> + <form> + <input type="hidden" defaultValue={process.env.REACT_APP_SECRET_CODE} /> + </form> + </div> + ); +} +``` + +During the build, `process.env.REACT_APP_SECRET_CODE` will be replaced with the current value of the `REACT_APP_SECRET_CODE` environment variable. Remember that the `NODE_ENV` variable will be set for you automatically. + +When you load the app in the browser and inspect the `<input>`, you will see its value set to `abcdef`, and the bold text will show the environment provided when using `npm start`: + +```html +<div> + <small>You are running this application in <b>development</b> mode.</small> + <form> + <input type="hidden" value="abcdef" /> + </form> +</div> +``` + +The above form is looking for a variable called `REACT_APP_SECRET_CODE` from the environment. In order to consume this +value, we need to have it defined in the environment. This can be done using two ways: either in your shell or in +a `.env` file. Both of these ways are described in the next few sections. + +Having access to the `NODE_ENV` is also useful for performing actions conditionally: + +```js +if (process.env.NODE_ENV !== 'production') { + analytics.disable(); +} +``` + +When you compile the app with `npm run build`, the minification step will strip out this condition, and the resulting bundle will be smaller. + +### Referencing Environment Variables in the HTML + +>Note: this feature is available with `react-scripts@0.9.0` and higher. + +You can also access the environment variables starting with `REACT_APP_` in the `public/index.html`. For example: + +```html +<title>%REACT_APP_WEBSITE_NAME% +``` + +Note that the caveats from the above section apply: + +* Apart from a few built-in variables (`NODE_ENV` and `PUBLIC_URL`), variable names must start with `REACT_APP_` to work. +* The environment variables are injected at build time. If you need to inject them at runtime, [follow this approach instead](#generating-dynamic-meta-tags-on-the-server). + +### Adding Temporary Environment Variables In Your Shell + +Defining environment variables can vary between OSes. It’s also important to know that this manner is temporary for the +life of the shell session. + +#### Windows (cmd.exe) + +```cmd +set "REACT_APP_SECRET_CODE=abcdef" && npm start +``` + +(Note: Quotes around the variable assignment are required to avoid a trailing whitespace.) + +#### Windows (Powershell) + +```Powershell +($env:REACT_APP_SECRET_CODE = "abcdef") -and (npm start) +``` + +#### Linux, macOS (Bash) + +```bash +REACT_APP_SECRET_CODE=abcdef npm start +``` + +### Adding Development Environment Variables In `.env` + +>Note: this feature is available with `react-scripts@0.5.0` and higher. + +To define permanent environment variables, create a file called `.env` in the root of your project: + +``` +REACT_APP_SECRET_CODE=abcdef +``` +>Note: You must create custom environment variables beginning with `REACT_APP_`. Any other variables except `NODE_ENV` will be ignored to avoid [accidentally exposing a private key on the machine that could have the same name](https://github.com/facebookincubator/create-react-app/issues/865#issuecomment-252199527). Changing any environment variables will require you to restart the development server if it is running. + +`.env` files **should be** checked into source control (with the exclusion of `.env*.local`). + +#### What other `.env` files can be used? + +>Note: this feature is **available with `react-scripts@1.0.0` and higher**. + +* `.env`: Default. +* `.env.local`: Local overrides. **This file is loaded for all environments except test.** +* `.env.development`, `.env.test`, `.env.production`: Environment-specific settings. +* `.env.development.local`, `.env.test.local`, `.env.production.local`: Local overrides of environment-specific settings. + +Files on the left have more priority than files on the right: + +* `npm start`: `.env.development.local`, `.env.development`, `.env.local`, `.env` +* `npm run build`: `.env.production.local`, `.env.production`, `.env.local`, `.env` +* `npm test`: `.env.test.local`, `.env.test`, `.env` (note `.env.local` is missing) + +These variables will act as the defaults if the machine does not explicitly set them.
+Please refer to the [dotenv documentation](https://github.com/motdotla/dotenv) for more details. + +>Note: If you are defining environment variables for development, your CI and/or hosting platform will most likely need +these defined as well. Consult their documentation how to do this. For example, see the documentation for [Travis CI](https://docs.travis-ci.com/user/environment-variables/) or [Heroku](https://devcenter.heroku.com/articles/config-vars). + +#### Expanding Environment Variables In `.env` + +>Note: this feature is available with `react-scripts@1.1.0` and higher. + +Expand variables already on your machine for use in your `.env` file (using [dotenv-expand](https://github.com/motdotla/dotenv-expand)). + +For example, to get the environment variable `npm_package_version`: + +``` +REACT_APP_VERSION=$npm_package_version +# also works: +# REACT_APP_VERSION=${npm_package_version} +``` + +Or expand variables local to the current `.env` file: + +``` +DOMAIN=www.example.com +REACT_APP_FOO=$DOMAIN/foo +REACT_APP_BAR=$DOMAIN/bar +``` + +## Can I Use Decorators? + +Many popular libraries use [decorators](https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841) in their documentation.
+Create React App doesn’t support decorator syntax at the moment because: + +* It is an experimental proposal and is subject to change. +* The current specification version is not officially supported by Babel. +* If the specification changes, we won’t be able to write a codemod because we don’t use them internally at Facebook. + +However in many cases you can rewrite decorator-based code without decorators just as fine.
+Please refer to these two threads for reference: + +* [#214](https://github.com/facebookincubator/create-react-app/issues/214) +* [#411](https://github.com/facebookincubator/create-react-app/issues/411) + +Create React App will add decorator support when the specification advances to a stable stage. + +## Fetching Data with AJAX Requests + +React doesn't prescribe a specific approach to data fetching, but people commonly use either a library like [axios](https://github.com/axios/axios) or the [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provided by the browser. Conveniently, Create React App includes a polyfill for `fetch()` so you can use it without worrying about the browser support. + +The global `fetch` function allows to easily makes AJAX requests. It takes in a URL as an input and returns a `Promise` that resolves to a `Response` object. You can find more information about `fetch` [here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). + +This project also includes a [Promise polyfill](https://github.com/then/promise) which provides a full implementation of Promises/A+. A Promise represents the eventual result of an asynchronous operation, you can find more information about Promises [here](https://www.promisejs.org/) and [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). Both axios and `fetch()` use Promises under the hood. You can also use the [`async / await`](https://davidwalsh.name/async-await) syntax to reduce the callback nesting. + +You can learn more about making AJAX requests from React components in [the FAQ entry on the React website](https://reactjs.org/docs/faq-ajax.html). + +## Integrating with an API Backend + +These tutorials will help you to integrate your app with an API backend running on another port, +using `fetch()` to access it. + +### Node +Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/). +You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo). + +### Ruby on Rails + +Check out [this tutorial](https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/). +You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo-rails). + +## Proxying API Requests in Development + +>Note: this feature is available with `react-scripts@0.2.3` and higher. + +People often serve the front-end React app from the same host and port as their backend implementation.
+For example, a production setup might look like this after the app is deployed: + +``` +/ - static server returns index.html with React app +/todos - static server returns index.html with React app +/api/todos - server handles any /api/* requests using the backend implementation +``` + +Such setup is **not** required. However, if you **do** have a setup like this, it is convenient to write requests like `fetch('/api/todos')` without worrying about redirecting them to another host or port during development. + +To tell the development server to proxy any unknown requests to your API server in development, add a `proxy` field to your `package.json`, for example: + +```js + "proxy": "http://localhost:4000", +``` + +This way, when you `fetch('/api/todos')` in development, the development server will recognize that it’s not a static asset, and will proxy your request to `http://localhost:4000/api/todos` as a fallback. The development server will **only** attempt to send requests without `text/html` in its `Accept` header to the proxy. + +Conveniently, this avoids [CORS issues](http://stackoverflow.com/questions/21854516/understanding-ajax-cors-and-security-considerations) and error messages like this in development: + +``` +Fetch API cannot load http://localhost:4000/api/todos. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. +``` + +Keep in mind that `proxy` only has effect in development (with `npm start`), and it is up to you to ensure that URLs like `/api/todos` point to the right thing in production. You don’t have to use the `/api` prefix. Any unrecognized request without a `text/html` accept header will be redirected to the specified `proxy`. + +The `proxy` option supports HTTP, HTTPS and WebSocket connections.
+If the `proxy` option is **not** flexible enough for you, alternatively you can: + +* [Configure the proxy yourself](#configuring-the-proxy-manually) +* Enable CORS on your server ([here’s how to do it for Express](http://enable-cors.org/server_expressjs.html)). +* Use [environment variables](#adding-custom-environment-variables) to inject the right server host and port into your app. + +### "Invalid Host Header" Errors After Configuring Proxy + +When you enable the `proxy` option, you opt into a more strict set of host checks. This is necessary because leaving the backend open to remote hosts makes your computer vulnerable to DNS rebinding attacks. The issue is explained in [this article](https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a) and [this issue](https://github.com/webpack/webpack-dev-server/issues/887). + +This shouldn’t affect you when developing on `localhost`, but if you develop remotely like [described here](https://github.com/facebookincubator/create-react-app/issues/2271), you will see this error in the browser after enabling the `proxy` option: + +>Invalid Host header + +To work around it, you can specify your public development host in a file called `.env.development` in the root of your project: + +``` +HOST=mypublicdevhost.com +``` + +If you restart the development server now and load the app from the specified host, it should work. + +If you are still having issues or if you’re using a more exotic environment like a cloud editor, you can bypass the host check completely by adding a line to `.env.development.local`. **Note that this is dangerous and exposes your machine to remote code execution from malicious websites:** + +``` +# NOTE: THIS IS DANGEROUS! +# It exposes your machine to attacks from the websites you visit. +DANGEROUSLY_DISABLE_HOST_CHECK=true +``` + +We don’t recommend this approach. + +### Configuring the Proxy Manually + +>Note: this feature is available with `react-scripts@1.0.0` and higher. + +If the `proxy` option is **not** flexible enough for you, you can specify an object in the following form (in `package.json`).
+You may also specify any configuration value [`http-proxy-middleware`](https://github.com/chimurai/http-proxy-middleware#options) or [`http-proxy`](https://github.com/nodejitsu/node-http-proxy#options) supports. +```js +{ + // ... + "proxy": { + "/api": { + "target": "", + "ws": true + // ... + } + } + // ... +} +``` + +All requests matching this path will be proxies, no exceptions. This includes requests for `text/html`, which the standard `proxy` option does not proxy. + +If you need to specify multiple proxies, you may do so by specifying additional entries. +Matches are regular expressions, so that you can use a regexp to match multiple paths. +```js +{ + // ... + "proxy": { + // Matches any request starting with /api + "/api": { + "target": "", + "ws": true + // ... + }, + // Matches any request starting with /foo + "/foo": { + "target": "", + "ssl": true, + "pathRewrite": { + "^/foo": "/foo/beta" + } + // ... + }, + // Matches /bar/abc.html but not /bar/sub/def.html + "/bar/[^/]*[.]html": { + "target": "", + // ... + }, + // Matches /baz/abc.html and /baz/sub/def.html + "/baz/.*/.*[.]html": { + "target": "" + // ... + } + } + // ... +} +``` + +### Configuring a WebSocket Proxy + +When setting up a WebSocket proxy, there are a some extra considerations to be aware of. + +If you’re using a WebSocket engine like [Socket.io](https://socket.io/), you must have a Socket.io server running that you can use as the proxy target. Socket.io will not work with a standard WebSocket server. Specifically, don't expect Socket.io to work with [the websocket.org echo test](http://websocket.org/echo.html). + +There’s some good documentation available for [setting up a Socket.io server](https://socket.io/docs/). + +Standard WebSockets **will** work with a standard WebSocket server as well as the websocket.org echo test. You can use libraries like [ws](https://github.com/websockets/ws) for the server, with [native WebSockets in the browser](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). + +Either way, you can proxy WebSocket requests manually in `package.json`: + +```js +{ + // ... + "proxy": { + "/socket": { + // Your compatible WebSocket server + "target": "ws://", + // Tell http-proxy-middleware that this is a WebSocket proxy. + // Also allows you to proxy WebSocket requests without an additional HTTP request + // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade + "ws": true + // ... + } + } + // ... +} +``` + +## Using HTTPS in Development + +>Note: this feature is available with `react-scripts@0.4.0` and higher. + +You may require the dev server to serve pages over HTTPS. One particular case where this could be useful is when using [the "proxy" feature](#proxying-api-requests-in-development) to proxy requests to an API server when that API server is itself serving HTTPS. + +To do this, set the `HTTPS` environment variable to `true`, then start the dev server as usual with `npm start`: + +#### Windows (cmd.exe) + +```cmd +set HTTPS=true&&npm start +``` + +#### Windows (Powershell) + +```Powershell +($env:HTTPS = $true) -and (npm start) +``` + +(Note: the lack of whitespace is intentional.) + +#### Linux, macOS (Bash) + +```bash +HTTPS=true npm start +``` + +Note that the server will use a self-signed certificate, so your web browser will almost definitely display a warning upon accessing the page. + +## Generating Dynamic `` Tags on the Server + +Since Create React App doesn’t support server rendering, you might be wondering how to make `` tags dynamic and reflect the current URL. To solve this, we recommend to add placeholders into the HTML, like this: + +```html + + + + + +``` + +Then, on the server, regardless of the backend you use, you can read `index.html` into memory and replace `__OG_TITLE__`, `__OG_DESCRIPTION__`, and any other placeholders with values depending on the current URL. Just make sure to sanitize and escape the interpolated values so that they are safe to embed into HTML! + +If you use a Node server, you can even share the route matching logic between the client and the server. However duplicating it also works fine in simple cases. + +## Pre-Rendering into Static HTML Files + +If you’re hosting your `build` with a static hosting provider you can use [react-snapshot](https://www.npmjs.com/package/react-snapshot) or [react-snap](https://github.com/stereobooster/react-snap) to generate HTML pages for each route, or relative link, in your application. These pages will then seamlessly become active, or “hydrated”, when the JavaScript bundle has loaded. + +There are also opportunities to use this outside of static hosting, to take the pressure off the server when generating and caching routes. + +The primary benefit of pre-rendering is that you get the core content of each page _with_ the HTML payload—regardless of whether or not your JavaScript bundle successfully downloads. It also increases the likelihood that each route of your application will be picked up by search engines. + +You can read more about [zero-configuration pre-rendering (also called snapshotting) here](https://medium.com/superhighfives/an-almost-static-stack-6df0a2791319). + +## Injecting Data from the Server into the Page + +Similarly to the previous section, you can leave some placeholders in the HTML that inject global variables, for example: + +```js + + + + +``` + +Then, on the server, you can replace `__SERVER_DATA__` with a JSON of real data right before sending the response. The client code can then read `window.SERVER_DATA` to use it. **Make sure to [sanitize the JSON before sending it to the client](https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0) as it makes your app vulnerable to XSS attacks.** + +## Running Tests + +>Note: this feature is available with `react-scripts@0.3.0` and higher.
+>[Read the migration guide to learn how to enable it in older projects!](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md#migrating-from-023-to-030) + +Create React App uses [Jest](https://facebook.github.io/jest/) as its test runner. To prepare for this integration, we did a [major revamp](https://facebook.github.io/jest/blog/2016/09/01/jest-15.html) of Jest so if you heard bad things about it years ago, give it another try. + +Jest is a Node-based runner. This means that the tests always run in a Node environment and not in a real browser. This lets us enable fast iteration speed and prevent flakiness. + +While Jest provides browser globals such as `window` thanks to [jsdom](https://github.com/tmpvar/jsdom), they are only approximations of the real browser behavior. Jest is intended to be used for unit tests of your logic and your components rather than the DOM quirks. + +We recommend that you use a separate tool for browser end-to-end tests if you need them. They are beyond the scope of Create React App. + +### Filename Conventions + +Jest will look for test files with any of the following popular naming conventions: + +* Files with `.js` suffix in `__tests__` folders. +* Files with `.test.js` suffix. +* Files with `.spec.js` suffix. + +The `.test.js` / `.spec.js` files (or the `__tests__` folders) can be located at any depth under the `src` top level folder. + +We recommend to put the test files (or `__tests__` folders) next to the code they are testing so that relative imports appear shorter. For example, if `App.test.js` and `App.js` are in the same folder, the test just needs to `import App from './App'` instead of a long relative path. Colocation also helps find tests more quickly in larger projects. + +### Command Line Interface + +When you run `npm test`, Jest will launch in the watch mode. Every time you save a file, it will re-run the tests, just like `npm start` recompiles the code. + +The watcher includes an interactive command-line interface with the ability to run all tests, or focus on a search pattern. It is designed this way so that you can keep it open and enjoy fast re-runs. You can learn the commands from the “Watch Usage” note that the watcher prints after every run: + +![Jest watch mode](http://facebook.github.io/jest/img/blog/15-watch.gif) + +### Version Control Integration + +By default, when you run `npm test`, Jest will only run the tests related to files changed since the last commit. This is an optimization designed to make your tests run fast regardless of how many tests you have. However it assumes that you don’t often commit the code that doesn’t pass the tests. + +Jest will always explicitly mention that it only ran tests related to the files changed since the last commit. You can also press `a` in the watch mode to force Jest to run all tests. + +Jest will always run all tests on a [continuous integration](#continuous-integration) server or if the project is not inside a Git or Mercurial repository. + +### Writing Tests + +To create tests, add `it()` (or `test()`) blocks with the name of the test and its code. You may optionally wrap them in `describe()` blocks for logical grouping but this is neither required nor recommended. + +Jest provides a built-in `expect()` global function for making assertions. A basic test could look like this: + +```js +import sum from './sum'; + +it('sums numbers', () => { + expect(sum(1, 2)).toEqual(3); + expect(sum(2, 2)).toEqual(4); +}); +``` + +All `expect()` matchers supported by Jest are [extensively documented here](https://facebook.github.io/jest/docs/en/expect.html#content).
+You can also use [`jest.fn()` and `expect(fn).toBeCalled()`](https://facebook.github.io/jest/docs/en/expect.html#tohavebeencalled) to create “spies” or mock functions. + +### Testing Components + +There is a broad spectrum of component testing techniques. They range from a “smoke test” verifying that a component renders without throwing, to shallow rendering and testing some of the output, to full rendering and testing component lifecycle and state changes. + +Different projects choose different testing tradeoffs based on how often components change, and how much logic they contain. If you haven’t decided on a testing strategy yet, we recommend that you start with creating simple smoke tests for your components: + +```js +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); +}); +``` + +This test mounts a component and makes sure that it didn’t throw during rendering. Tests like this provide a lot of value with very little effort so they are great as a starting point, and this is the test you will find in `src/App.test.js`. + +When you encounter bugs caused by changing components, you will gain a deeper insight into which parts of them are worth testing in your application. This might be a good time to introduce more specific tests asserting specific expected output or behavior. + +If you’d like to test components in isolation from the child components they render, we recommend using [`shallow()` rendering API](http://airbnb.io/enzyme/docs/api/shallow.html) from [Enzyme](http://airbnb.io/enzyme/). To install it, run: + +```sh +npm install --save enzyme enzyme-adapter-react-16 react-test-renderer +``` + +Alternatively you may use `yarn`: + +```sh +yarn add enzyme enzyme-adapter-react-16 react-test-renderer +``` + +As of Enzyme 3, you will need to install Enzyme along with an Adapter corresponding to the version of React you are using. (The examples above use the adapter for React 16.) + +The adapter will also need to be configured in your [global setup file](#initializing-test-environment): + +#### `src/setupTests.js` +```js +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); +``` + +>Note: Keep in mind that if you decide to "eject" before creating `src/setupTests.js`, the resulting `package.json` file won't contain any reference to it. [Read here](#initializing-test-environment) to learn how to add this after ejecting. + +Now you can write a smoke test with it: + +```js +import React from 'react'; +import { shallow } from 'enzyme'; +import App from './App'; + +it('renders without crashing', () => { + shallow(); +}); +``` + +Unlike the previous smoke test using `ReactDOM.render()`, this test only renders `` and doesn’t go deeper. For example, even if `` itself renders a `