Build an old-fashioned agenda using Decker (HyperCard) and Ruby

Build an old-fashioned agenda using Decker (HyperCard) and Ruby

7 min read

Do you know what HyperCard is? What about Decker? If you’re unfamiliar with any of those two, you’re missing a lot, but no worries, in this post we’re going to build an old-fashioned agenda using Decker (HyperCard) and the Ruby Nylas APIs.

What are we going to talk about?

What is HyperCard?

HyperCard combines a flat-file database, a graphical and flexible interface with an easy-to-use programming language called HyperTalk. It was released in 1987 (35 years ago) and it was the first successful hypermedia system. It was used to make applications, games, interactive guides and more:

Macintosh's HyperCard

What is Decker?

Decker is a multimedia platform for creating and sharing interactive documents, with sound, images, hypertext, and scripted behaviour. Its programming language is called Lil.

Based on HyperCard, Decker retains the old-fashioned look and feel of the classic MacOS.

Decker, modern take on HyperCard

Is your system ready?

If you already have the Nylas Ruby SDK installed and your Ruby environment is configured, then continue reading this blog.

Otherwise, I recommend reading the post How to Send Emails with the Nylas Ruby SDK to understand the setup basics.

What our application will look like

We’re going to use the Nylas Ruby APIs to fetch our contacts, and then create a .deck file that can be opened in Decker. As we’re going to use profile images, we’re going to download them and use Lilt (A command-line interface for Lil, Decker’s scripting language) to convert them into an encoded string that Decker will interpret as a pixelated, black-and-white image.

Here are a couple of screenshots so you can see what I mean:

Agenda built using Decker

This is the main screen. Here we can press Enter to start browsing our contacts.

First contact on our Decker Agenda

If you’re into wrestling, and you’re old enough, you should know Hulk.

Another contact on our Decker Agenda

You can see here that they are coming from my contacts:

List of contacts

Installing Decker

The best and easiest way to install Decker is to download (you can pay to support the developer) the .zip file for your operating system from here:

Installing Decker

Installing the MiniMagick gem

First, we need to install the Image Magick package for our operating system here, or we can simply use brew:

$ brew install imagemagick

And then install the MiniMagick gem:

$ gem install mini_magick

Installing Lilt

In order to install Lilt, we need to download the source code and then run the following command:

$ make lilt && make install

Creating the agenda application

We’re going to create a folder and call it Address_Book (feel free to call it something else). Inside, create a file called address_book.rb with the following source code (The deckbuilder section has been reduced to save space, you can check the complete source code here):

# frozen_string_literal: true

# Import your dependencies
require 'nylas'
require 'dotenv/load'
require 'mini_magick'

# Initialize your Nylas API client
nylas = Nylas::API.new(
  app_id: ENV['CLIENT_ID'],
  app_secret: ENV['CLIENT_SECRET'],
  access_token: ENV['ACCESS_TOKEN']
)

deck_source = '{deck}' + "\n" + 'version:1' + "\n" + 'card:0' + "\n" + 'size:[512,342]' + "\n" +
 'name:"contacts.deck"' + "\n\n" + '{fonts}' + "\n" + 'deckbuilder:"%%FNT0GCA"' + "\n\n" + '{card:home}' + 
 "\n" + '{widgets}' + "\n" + 'Title:{"type":"field","size":[149,38],"pos":[177,90],"font":"deckbuilder",
 "style":"plain","align":"center","value":"Contacts"}' + "\n" + 'enter:{"type":"button","size":
 [60,20],"pos":[222,213],"script":"home.0","text":"Enter"}' + "\n" + 'field1:{"type":"field",
 "size":[149,49],"pos":[177,140],"font":"menu","style":"plain","align":"center", "value":"An address 
 book\ncreated using Decker and Nylas"}' + "\n\n" + '{script:home.0}' + "\n" + 'on click do' + "\n" + '  
 go["Next" "SlideDown"]' + "\n" + 'end' + "\n" + '{end}' + "\n\n"

# Fetch contacts
contacts = nylas.contacts.where(source: "address_book")
# Needed to call scripts
first_card = contacts[0].given_name + contacts[0].surname.gsub(/\s.*/,'') + '.0'
first_card_script = contacts[0].given_name + contacts[0].surname.gsub(/\s.*/,'') + '.1'
counter = 0
# Loop through all contacts
contacts.each do |contact|
	counter = counter + 1
            # Get the profile picture
	picture = contact.picture
	file_name = 'profile.png'
            # Save the profile picture
	File.open(file_name,"wb") do |f|
	    f.write File.open(picture, 'rb') {|file| file.read }
	end
            # Use MiniMagick to resize and reformat the image
	image = MiniMagick::Image.open("profile.png")
	image.resize "100x100"
	image.format "gif"
	image.write "profile.gif"

            # Call the Lil script
	cmd = `lilt encode.lil`
	
           # Make sure nothing is empty	
           if(contact.nickname == nil)
	    contact.nickname = ''
	end

	if(contact.birthday == nil)
	    contact.birthday = ''
	end
	
	if(contact.emails[0].email == nil)
	    contact.emails[0].email = ''
	end	
	
            # Generate the information for the .deck file
	deck_source += '{card:' + contact.given_name + contact.surname.gsub(/\s.*/,'') + '}' + 
                       "\n" + '{widgets}'  + "\n" +  'profile:{"type":"canvas","size":[100,100],
                       "pos":[30,90],"show":"transparent","border":0,"image":"' + cmd + '","scale":1}' + 
                       "\n" + 'field1:{"type":"field","size":[78,20],"pos":[184,76],"font":"menu",
                       "style":"plain","value":"Given name:"}' + "\n" +	'givenname:{"type":"field",
                       "size":[173,20],"pos":[275,76],"font":"mono","style":"plain","align":"center",
                       "value":"' + contact.given_name + '"}' + "\n" + 'field2:{"type":"field",
                       "size":[78,20],"pos":[184,104],"font":"menu","style":"plain","value":"Nickname:"}' + 
                       "\n" + 'nickname:{"type":"field","size":[174,20],"pos":[274,103],"font":"mono",
                       "style":"plain","align":"center","value":"' + contact.nickname + '"}' + "\n" +
                       'field3:{"type":"field","size":[78,20],"pos":[184,131],"font":"menu",
                       "style":"plain","value":"Surname:"}' + "\n" + 'surname:{"type":"field",
                       "size":[174,20],"pos":[274,130],"font":"mono","style":"plain","align":"center",
                       "value":"' + contact.surname + '"}' + "\n" + 'field4:{"type":"field",
                       "size":[78,20],"pos":[184,158],"font":"menu","style":"plain","value":"Birthday:"}' + 
                       "\n" + 'birthday:{"type":"field","size":[175,20],"pos":[274,157],"font":"mono",
                       "style":"plain","align":"center","value":"' + contact.birthday + '"}' + "\n" +	
                       'field5:{"type":"field","size":[78,20],"pos":[184,185],"font":"menu","style":"plain",
                       "value":"Email:"}' + "\n" +'email:{"type":"field","size":[176,20],"pos":[274,184],
                       "font":"mono","style":"plain","align":"center","value":"' + contact.emails[0].email + 
                       '"}' + "\n" + 'Next:{"type":"button","size":[60,20],"pos":[258,305],
                       "script":"home.0","text":"Next"}' + "\n" + 'Previous:{"type":"button","size":[60,20],
                       "pos":[182,305],"script":"' + first_card_script + '","text":"Previous"}' + "\n\n" +
	               '{script:' + first_card_script + '}' + "\n" + 'on click do' + "\n" + '  go["Prev" 
                       "SlideDown"]' + "\n" + 'end' + "\n" + '{end}' + "\n\n"
	if(counter == 1)
	    deck_source += '{script:' + first_card + '}' + "\n" + 'on view do' + "\n" + '  go[0]' + "\n" + 
            'end' + "\n" + '{end}' + "\n\n"
	end
end

# Write the source code for the .deck file
File.open("addressbook.deck", "wb") { |f| f.puts "#{deck_source}" }

Before we can run this, we need to create a Lil application to be opened by Lilt, we will call it encode.lil:

print[read["profile.gif" "gray"].transform["dither"].encoded]

This script will make the image grayscale, pixelate it and then return the encoded string.

Running our agenda application

In order to run our application, we need to type the following on a terminal screen:

$ ruby address_book.rb

And then open the generated addressbook.deck on Decker.

We just build an old-fashioned agenda using Decker (HyperCard) and Ruby.

Don’t miss the action, join our LiveStream Coding with Nylas:

If you want to learn more about the Nylas Contacts API, please visit our documentation Contacts API Overview.

Related resources

How to create an appointment scheduler in your React app

Learn how to create an appointment scheduler in your React app using Nylas Scheduler. Streamline bookings and UX with step-by-step guidance.

Beyond APIs: Designing elegant, smart Web Components

Dive into the world of smart Web Components for advanced API integration solutions. Design elegant, customizable elements to elevate user experiences.

How to integrate a React calendar component to streamline scheduling

Elevate your app’s scheduling with a React calendar component and seamlessly integrate with Nylas for efficient calendar management.