Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save cyrusfirheir/c8b47ca3af805964b5ebe1d51f5a7d2e to your computer and use it in GitHub Desktop.

Select an option

Save cyrusfirheir/c8b47ca3af805964b5ebe1d51f5a7d2e to your computer and use it in GitHub Desktop.
CTP macro-set for SugarCube 2

Overview

This set of macros/functions aim to provide an easy way to set up content which is revealed bit-by-bit via user interaction.

Using nested <<linkreplace>> and <<linkappend>> works, but gets tedious and is often prone to errors. The CTP (Click To Proceed: original-est name ever) macros make it a bit easier by turning them into blocks instead of nests.

Installation

If using the Twine desktop/web app, copy contents of CTP.js to Story JavaScript, and contents of CTP.css to Story Stylesheet.

If using a compiler like Tweego, drop CTP.js and CTP.css to your source folder.

Example Usage

<<ctp "testID">>
  This is the first string.
<<ctpNext clear>>
  Second! It cleared the first one out!
<<ctpNext nobr>>
  Third, but with nobr...
<<ctpNext t8n>>
  And the final one. With a transition!
<</ctp>>

<<link "Next">>
  <<ctpAdvance "testID">>
<</link>>

Macros

NOTE: Avoid usage of passage navigation macros until the end of the entire chain. To free up memory when not required anymore, CTP objects created via the macros are deleted once they reach the end. If the passage changes before that happens, then the object will stay in memory and bloat the State (and affect performance; and the size of save files) unnecessarily.

<<ctp "id">>

  • id: (string) Unique ID to be used to identify the chain of content. Naming rule follows the same as those of SugarCube variable names (learn more here).

Example:

<<ctp "ID_of_the_year">>
  Bare minimum...
<</ctp>>

<<ctpNext [keywords]>>

To be used inside <<ctp>> to separate the content into blocks.

  • keywords: (optional|string) The following keywords can be used to alter behavior of the macro:
    • clear: Clears content of previous block. Use for replacing.
    • nobr: Appends content in the same line as last block instead of going to a new line.
    • t8n or transition: Custom CSS animation based transition (250ms fade-in by default).

Example:

<<ctp "fancyCTP">>
  One.
<<ctpNext clear>>
  Two with clear.
<<ctpNext nobr>>
  Three on the same line.
<<ctpNext t8n>>
  Fading four.
<</ctp>>

<<ctpAdvance "id">>

The 'proceed' part of Click To Proceed... Used to move the train forward and show the next blocks.

  • id: (string) Unique ID which was set up in <<ctp>>.

NOTE: Use with user interaction (inside a <<link>> or <<button>>) or inside a <<timed>> macro to ensure the DOM is loaded and has the element on page for the macro to target.

Example:

<<ctp "ID_of_the_year_once_again">>
  <!-- stuff -->
<</ctp>>

<<link "Next">>
  <<ctpAdvance "ID_of_the_year_once_again">>
<</link>>

JavaScript usage - The CTP object

The CTP custom object is set up as follows:

id: (string) Unique ID.

selector: (string) CSS selector to target to output to. When used by the macro, this is the slugified form of id.

Example:

var ctpTest = new CTP({
  id: "ctpTest",
  selector: "#ctp-test-id"
});

Other properties which are used under the hood:

stack: (array) Contains the content of all blocks.

log: (object) Keeps track of blocks amd their behaviors:

  • index: (whole number) Current index of block (zero-based).
  • clear: (array) Indices of blocks at which to clear the output element before appending new content.
  • nobr: (array) Indices of blocks where no <br> is added.
  • transition: (array) Indices of blocks which have the CSS transition (Target the class .macro-ctp-entry-t8n to customize).

Object methods:

CTP.add(content [, keywords])

Adds content to the end of the stack and returns the CTP object for chaining.

  • content: (string) The actual content in the block.
  • keywords: (string) Space-separated list of keywords (clear, nobr, t8n, transition) to modify behavior of the blocks.

Example:

ctpTest
  .add("This is the first string.")
  .add("Second! It cleared the first one out!", "clear")
  .add("Third, but with nobr...", "nobr")
  .add("And the final one. With a transition!", "t8n");

CTP.advance()

Does the same as <<ctpAdvance>>, moving to the next block. Returns the CTP object for chaining.

Example:

ctpTest.advance();

CTP.ctpEntry(index)

Returns the html output for a single block at the index passed into it.

  • index: (whole number) Index of block to return.

Example:

ctpTest.ctpEntry(2);

// Assuming ctpTest is the same as in the previous examples, this returns:
// <span class="macro-ctp-entry macro-ctp-entry-index-2">Third, but with nobr...</span>

CTP.out()

Returns the html output for entire chain from the last 'clear' to the current index.

Example:

// Assuming current index is 3
ctpTest.out()

/* Returns:
 *
 * <span class="macro-ctp-entry macro-ctp-entry-index-1">Second! It cleared the first one out!</span>
 * <span class="macro-ctp-entry macro-ctp-entry-index-2">Third, but with nobr...</span>
 * <br>
 * <span class="macro-ctp-entry macro-ctp-entry-index-3">And the final one. With a transition!</span>
 */

Complete usage:

JavaScript:

State.variables.ctpTest = new CTP({
  id: "ctpTest",
  selector: "#ctp-test-id"
});

State.variables.ctpTest
  .add("This is the first string.")
  .add("Second! It cleared the first one out!", "clear")
  .add("Third, but with nobr...", "nobr")
  .add("And the final one. With a transition!", "t8n");

In Passage:

<div id="#ctp-test-id">
  <<= $ctpTest.out()>>
</div>

<<link "Advance">>
  <<run $ctpTest.advance()>>
  <!-- Because $ctpTest was created manually, using the <<ctpAdvance>> macro won't work. To be able to use <<ctpAdvance>>, the CTP object needs to be set as a property of State.variables.macroCTP as that is what is used internally to store CTP objects created via the macros. -->
<</link>>

TODO

  • Add CSS time keyword to delay before next block appears.
.macro-ctp-entry-t8n {
opacity: 1;
animation: macro-ctp-fade-in 0.25s ease;
}
@keyframes macro-ctp-fade-in {
0% {
opacity: 0;
}
}
window.CTP = function (config) {
this.id = "";
this.selector = "";
this.stack = [];
this.log = {
index: 0,
clear: [0],
nobr: [],
transition: []
};
this.add = function (entry) {
var mods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
var ctp = this;
this.stack.push(entry);
mods.replace("t8n", "transition").split(" ").forEach(function (el) {
if (el) ctp.log[el].pushUnique(ctp.stack.length - 1);
});
return this;
};
this.ctpEntry = function (index) {
if (index < 0 || index >= this.stack.length) return "";
var t8n = this.log.transition.includes(index) ? "macro-ctp-entry-t8n" : "";
var br = index === 0 || this.log.clear.includes(index) ? " " : this.log.nobr.includes(index) ? " " : "<br>";
return br + '<span class="macro-ctp-entry macro-ctp-entry-index-' + index + " " + t8n + '">' + this.stack[index] + "</span>";
};
this.advance = function () {
if (this.log.index === this.stack.length - 1) return this;
var ctp = this;
var index = ++this.log.index;
var _el = $(this.selector);
if (this.log.clear.includes(index)) _el.empty();
_el.wiki(ctp.ctpEntry(index));
return this;
};
this.out = function () {
var ctp = this;
var clear = ctp.log.clear;
var clearIndex = ctp.log.index > 0 ? clear.findIndex(function (el) {
return el >= ctp.log.index;
}) - 1 : 0;
var ret = ctp.stack.map(function (el, index) {
return index;
}).slice(clearIndex, ctp.log.index + 1).reduce(function (acc, cur) {
return acc + ctp.ctpEntry(cur);
}, "");
return ret;
};
Object.keys(config).forEach(function (pn) {
this[pn] = clone(config[pn]);
}, this);
};
CTP.prototype.clone = function () {
return new CTP(this);
};
CTP.prototype.toJSON = function () {
var ownData = {};
Object.keys(this).forEach(function (pn) {
ownData[pn] = clone(this[pn]);
}, this);
return JSON.reviveWrapper('new CTP($ReviveData$)', ownData);
};
Macro.add("ctp", {
tags: ["ctpNext"],
handler: function handler() {
var _id = this.args[0];
var _selector = "#" + Util.slugify(_id);
var ctp = new CTP({
id: _id,
selector: _selector
});
this.payload.forEach(function (el, index) {
var _args = el.args;
if (el.name === "ctp") _args.reverse().pop();
ctp.add(el.contents.trim(), _args.join(" "));
});
variables().macroCTP = variables().macroCTP || {};
variables().macroCTP[_id] = ctp;
$(this.output).wiki('<span id="' + Util.slugify(_id) + '" class="macro-ctp-wrapper">' + ctp.out() + "</span>");
}
});
Macro.add("ctpAdvance", {
handler: function handler() {
var _id = this.args[0];
variables().macroCTP = variables().macroCTP || {};
var ctp = variables().macroCTP[_id];
if (ctp) {
ctp.advance();
if (ctp.log.index === ctp.stack.length - 1) {
delete variables().macroCTP[_id];
}
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment