Storyspace 3: digging a bit deeper with attributes, prototypes, and actions

Storyspace 3 has very rich scripting features, although at present itself is not scriptable using AppleScript. This brief tutorial looks at some of these, and how they can be incorporated to do some fairly unusual things for an authoring environment.

Prototypes

Most tutorials work through small parts of projects, but in reality, particularly when embarking on a substantial hypertext project, you will be working with scores or even hundreds of writing spaces. It is very tempting to leave these unstructured, in which case you will find yourself repeating mundane tasks over and over again.

One valuable tool to save you from that is the prototype: create one, set it up with custom attributes and so on, and use that as the basis for writing spaces with similar functions and features. For example, where my Trees in the Landscape hypertext book is clearly going to use dozens of writing spaces about paintings, it is worth making a PaintingType prototype from which those can be made.

Create a writing space in the usual way, and name it PaintingType. To turn it into a prototype, select that writing space in the Map view, open the Inspector, switch to the Properties Inspector, select the Prototype tab, and check the Prototype box.

sspace31Custom attributes

All our paintings have common information, about the name of the artist, their dates, the name of the painting, its year, and so on. Rather than having to keep entering these in unstructured text in each writing space, it can be valuable to store these in custom attributes, which are then accessible to other writing spaces.

sspace32In the Inspector, switch to the Document Inspector, and select the User tab. In the popup tool menu next to the empty Attribute list, use the New user attribute command to create a new attribute. Give it the name PArtist, with a string type, no default value, and some informative text in the description. Repeat this for other attributes, until there is one for each of the pieces of information we require about each painting.

sspace33

Those custom attributes are now available throughout this document, but we will expose them for editing only in those writing spaces based on the PaintingType prototype. Click on the + button at the top right of the Outline view, scroll down to the list of User attributes, and check each one to add it to the top of the Outline view.

sspace34

Using the prototype and attributes

Create a new writing space in the usual way, and give it a working title such as ConstableValeOfDedham. As there is now a prototype available, the lower right edge of the writing space icon in the Map view offers a tab, in which you should select the PaintingType prototype. As soon as you do, all the custom attributes become visible and editable in the Outline view. Complete them as shown.

sspace35With those attributes, we can generate the text content of the writing space automatically. Although in this case it is not likely to change much, one way of ensuring that it is freshly generated when you open the writing space is to build it each time the space is visited, using the OnVisit action. We can add this to the prototype, which ensures that it will happen for every child writing space.

sspace36Select the PaintingType icon in the Map view, then in the Outline view click on the + button to add another attribute, the OnVisit action, in the Storyspace list. Insert the following action script to generate the text content from our custom attributes:
$Text=$PArtist+" ("+$PLifeYears+"), "+PTitle+" ("+PMadeYear+"), "+PMedia+", "+PDimensions+" cm, "+PLocation+". "+PCredit+"."
which simply concatenates together all the elements in the string.

sspace37Switch the Outline view to Read, and click on the ConstableValeOfDedham writing space in the Map view. The text content should now be generated automatically, and correctly formatted for display.

sspace38Viewing the painting in another app

We can also use the OnVisit action to open a full-size image of the painting using another app, such as Preview, rather than making the reader fiddle around with the writing space window to get it to display the painting in its full glory. Storyspace 3 gives you access to OS X command line tools with its runCommand command, and we can launch Preview and open a given JPEG image using a Terminal command such as
open -b com.apple.Preview ~/Documents/00sspace/myImage.jpg

We will here hard-code the folder location of our paintings for the sake of simplicity. Create a folder in your Documents folder named 00sspace, and put a test JPEG image there named constablevalededhamfull.jpg. In the PaintingType prototype, create another custom user attribute named PImage, to hold a string containing the image file name. Then select the OnVisit action, move the cursor to its (right) end, and append the script to open the file whose name is in the PImage attribute:
; runCommand("open -b com.apple.Preview ~/Documents/00sspace/"+$PImage)
The opening semi-colon allows this second command line after that generating the text content of the writing space.

sspace39Now open the ConstableValeOfDedham writing space, and insert its image file name, constablevalededhamfull.jpg, as the value of its PImage attribute. Whenever you click on that writing space, it should now open that image using Preview, if not already open.

sspace310There is an interesting side-effect: as the prototype does not contain a file name in its PImage attribute, the command which it will pass will open all images in the folder. You can work around this using conditionals, or by setting the prototype to open just a small, dummy image.

The completed Storyspace 3 file for this is here: TitLf29

Doing more with runCommand

This is a potentially very powerful feature, which can also run AppleScripts if needed. As another example, I will consider a very different non-fiction hypertext book, about OS X. This links in with a recent series of articles here about CoreStorage.

Using the shell command
diskutil cs list
we can get a pretty-printed structured list of CoreStorage objects, which is of value. Within that, there will be a long UUID for the Logical Volume Group (LVG); using that (which I will here call LVGUUID), the command
diskutil cs info LVGUUID
will give further information.

We can call those commands using Storyspace’s runCommand feature.

In a new, empty document, create a writing space named CoreStorageList. Add two attributes, MyString from the Sandbox list, and onVisit from Storyspace. We will use MyString to store the UUID drawn from the OnVisit action. Edit OnVisit to contain the following script:
$Text = runCommand(diskutil cs list); $MyString = $Text.substr(71,36)

sspace311

This first of all calls the
diskutil cs list
command, and puts the result into the Text field. The second line of the action then extracts the LVG UUID and stores it in the MyString attribute. Taking a fixed sub-string like this is of course error-prone, and I should really use Storyspace’s regular expression features to do the job properly, but it will suffice for this example.

sspace312As soon as you click on the CoreStorageList writing space to visit it, the action will run, and you should see details of the CoreStorage LVG, if found, with the LVG UUID placed into MyString.

We can now use that UUID in another writing space, to call the next command. Create a new writing space, and link start to CoreStorageList, and that to the new space, named CoreStorageInfo, using simple text links. Add an OnVisit action to CoreStorageInfo, containing the script
$Text = runCommand("diskutil cs info "+$MyString(/CoreStorageList))
which concatenates the contents of the MyString attribute of the CoreStorageList writing space, containing the LVG UUID, to build the shell command, and writes the result into the text content of the space.

sspace313

Switch to Read mode, select the start writing space, and press Return to move down the chain. If your Mac has a Fusion Drive or other CoreStorage LVG, you will see successive details in the spaces.

sspace314

The completed Storyspace 3 file for this is here: CoreStorage