Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

JSCore

Jscore or jscore.ewwii is an official JavaScript language support plugin for ewwii. It aims to bring the familiarity of JavaScript to the powerful backend of ewwii. This plugin is built for for users who want the maximum power and the speed at the cost of an heavy Just-In-Time JS engine.

One of the major differences between JSCore and other ewwii language support plugins is that it opts for a modern, self-sufficient architecture where all the configuration can be done directly from the comfort of your javascript file without having to rely on external scripts.

Features

  • npm support: Wide range of packages from JS ecosystem
  • v8 engine backend: Blazingly fast evaluation
  • self-sufficient: No need for external scripts

Installation

You can use the following command to install jscore using eiipm.

# if you prefer prebuilt
eiipm add "Ewwii-sh/jscore.ewwii" --prebuilt --ref <version>

# if you prefer building yourself
eiipm add "Ewwii-sh/jscore.ewwii"                  # Up to date with "main" branch
eiipm add "Ewwii-sh/jscore.ewwii" --ref <version>  # Locked to a specific version

Or you can download it manually from Github Releases.

If you are downloading from the GitHub Releases, make sure to create a plugins/ directory in your ewwii configuration and put the libjscore.so in there.

Configuration

Jscore follows a fundamentally different style of configuration compared to ewwii. Ewwii, with rhai and nbcl, both capable languages, went for a declarative style of configuration for ease of use. However, jscore embraces the imperative nature of JavaScript and builds upon it to create a flexible and powerful configuration system.

Basics

The entry file of jscore is ewwii.js. As always, create the entry file (in this case ewwii.js), and the ewwii.(s)css to start your configuration off.

Once the files are created, you can start by first importing the widgets:

import * as Widgets from "ewwii/widgets";

This contains all the widgets that you will use to build your configuration.

Creating A Widget

Let's create a label, shall we? To create a label, call the Label function under the Widgets namespace like so:

let myLabel = Widgets.Label();

This creates an empty label. To create a label that holds a value, initialize like so:

let myLabel = Widgets.Label("Hello, World!");

Don't worry about widgets taking in parameters too much for now. Only a few really does that, and it mostly is a syntactical sugar.

Assigning Properties

Now that we have a widget, we should be able to tweak the properties of it. There are two ways in which you can do that. There is no right way to set properties. You can use the method you prefer.

1. Through properties function.

let myLabel = Widgets.Label().properties({
    truncate: true,
    widget_name: "my_label"
});

The properties function takes in an object (i.e json) which provides a list of properties to set.

2. Through the set_property function.

let myLabel = Widgets.Label();
myLabel.set_property("truncate", true);
myLabel.set_property("widget_name", "my_label");

The set_property function takes in a key and a value. The key being the name of the property, and the value being the value of the property to set.

Regarding earlier Widgets.Label("Hello, World!").

let myLabel = Widgets.Label("Hello, World!");

Is syntactical sugar for:

let myLabel = Widgets.Label().properties({
    text: "Hello, World!"
});

Appending Children

Some widgets like Box takes in children. You can add a child to these types of container widgets using the add_child function.

let myLabel = Widgets.Label("Hello, World!");

let myBox = Widgets.Box();
myBox.add_child(myLabel); // set label as child

You would use the same add_child property to set the root child of a window.

Creating a Window

This is the final thing you need to learn to render your first widget. We put together everything we learned till now to render a window that shows "Hello, World!".

let myLabel = Widgets.Label("Hello, World!");

let myWindow = Widgets.Window("window_name");
myWindow.add_child(mylabel);

Now the final step is to register this window to ewwii so that you can open it with a simple command like ewwii open window_name.

Widgets.register(myWindow);

This will register your window to ewwii.

Full Example

Here is an example code that puts everything together to render a window that shows "Hello, World!":

import * as Widgets from "ewwii/widgets";

let myLabel = Widgets.Label("Hello, World!");
let myBox = Widgets.Box();

myBox.add_child(myLabel);

let myWindow = Widgets.Window("my_window");
myWindow.add_child(myBox);

Widgets.register(myWindow);

Fabulous!

Widget Properties

All the widget supported in jscore and its properties. Just for demonstration, here is how to use Revealer widget and its properties:

import * as Widgets from "ewwii/widgets";

let myRevealer = Widgets.Revealer().properties({ transition: "slideright", reveal: true, duration: "500ms" });
let childLabel = Widgets.Label("Hello!"); myRevealer.add_child(childLabel);

All

These properties apply to all widgets, and can be used on every widget!

Properties

  • class: string css class name
  • valign: string how to align this vertically. possible values: "fill", "baseline", "center", "start", "end"
  • halign: string how to align this horizontally. possible values: "fill", "baseline", "center", "start", "end"
  • vexpand: bool should this container expand vertically. Default: false
  • hexpand: bool should this widget expand horizontally. Default: false
  • eval_ignore: bool skip the automatic re-evaluation of this widget.
  • width: int width of this element
  • height: int height of this element
  • active: bool If this widget can be interacted with
  • tooltip: string tooltip text (on hover)
  • visible: bool visibility of the widget
  • style: string inline scss style applied to the widget
  • css: string scss code applied to the widget
  • can_target: bool make the widget targettable to pointer events.
  • focusable: bool make widget focusable
  • widget_name: string custom widget name

ComboBoxText

Properties

  • items: vec Items displayed in the combo box
  • timeout: duration timeout of the command. Default: "200ms"
  • onchange: string runs when an item is selected, replacing {} with the item

Expander

Properties

  • name: string name of the expander
  • expanded: bool sets whether it's expanded

Revealer

Properties

  • transition: string animation name ("slideright", "slideleft", etc.)
  • reveal: bool whether the child is revealed
  • duration: duration how long the transition lasts. Default: "500ms"

Checkbox

Properties

  • checked: bool initial checked state
  • timeout: duration command timeout. Default: "200ms"
  • onchecked: string command when checked
  • onunchecked: string command when unchecked

ColorButton

Properties

  • use_alpha: bool use alpha channel
  • onchange: string command on color select
  • timeout: duration Default: "200ms"

ColorChooser

Properties

  • use_alpha: bool use alpha channel
  • onchange: string command on color select
  • timeout: duration Default: "200ms"

Scale

Properties

  • flipped: bool reverse direction
  • marks: string draw marks
  • draw_value: bool show value
  • value_pos: string where to show value ("left", "right", etc.)
  • round_digits: int number of decimal places
  • value: float current value
  • min: float minimum value
  • max: float maximum value
  • timeout: duration Default: "200ms"
  • onchange: string command on change (use {} for value)
  • orientation: string layout direction

Progress

Properties

  • text: text to show over the progress
  • show_text: whether to show the text
  • flipped: bool reverse direction
  • value: float progress (0–100)
  • orientation: string layout direction

CircularProgress

Properties

  • value: float the progression value, between (0-100)
  • start_at: float the percentage that the circle should start at
  • thickness: float the thickness of the circle
  • clockwise: bool wether the progress bar spins clockwise or counter clockwise
  • fg_color: string foreground color of circle
  • bg_color: string background color of circle

Input

Properties

  • value: string set current text
  • placeholder: string set a placeholder text
  • onchange: string command on change (use {} for value)
  • timeout: duration Default: "200ms"
  • onaccept: string command on Enter (use {} for value)
  • password: bool obscure input

Button

Properties

  • label: string label to show on the button
  • timeout: duration Default: "200ms"
  • onclick: string command on activation
  • onmiddleclick: string command on middle click
  • onrightclick: string command on right click

Image

Properties

  • path: string image file path
  • content_fit: string how the image should fit ("fill", "contain", "cover", "scaledown")
  • can_shrink: bool whether the image can shrink or not
  • image_width: int image width
  • image_height: int image height
  • preserve_aspect_ratio: bool keep aspect ratio
  • fill_svg: string fill color for SVGs

Box

Properties

  • spacing: int spacing between children
  • orientation: string direction of children
  • space_evenly: bool distribute children evenly

Overlay

Properties

None

Tooltip

Properties

None listed

Scroll

Properties

  • hscroll: bool allow horizontal scrolling
  • vscroll: bool allow vertical scrolling
  • propagate_natural_height: bool use the natural height if true

EventBox

Properties

  • orientation: string layout direction
  • timeout: duration Default: "200ms"
  • onscroll: string command on scroll ({} becomes direction)
  • onhover: string command on hover
  • onhoverlost: string command on hover exit
  • cursor: string cursor type
  • ondropped: string command on drop ({} is URI)
  • dragvalue: string URI to drag from this widget
  • dragtype: string type to drag ("file", "text")
  • onclick: string command on click
  • onmiddleclick: string command on middle click
  • onrightclick: string command on right click
  • onkeypress: string command on any key press ({} becomes the id of the key pressed)
  • onkeyrelease: string command on any key release ({} becomes the id of the key released)

Label

Properties

  • text: string text to display
  • truncate: bool truncate text
  • limit_width: int max characters to show
  • truncate_left: bool truncate beginning
  • unindent: bool strip leading spaces
  • unescape: bool parses escape sequences
  • markup: string Pango markup
  • wrap: bool wrap text
  • gravity: string text gravity
  • xalign: float horizontal alignment
  • yalign: float vertical alignment
  • justify: string text justification
  • wrap_mode: string wrap mode ("word", "char", etc.)
  • lines: int max lines (−1 = unlimited)

Calendar

Properties

  • day: float selected day
  • month: float selected month
  • year: float selected year
  • show_heading: bool show heading
  • show_day_names: bool show day names
  • show_week_numbers: bool show week numbers
  • onclick: string command with {0}, {1}, {2} for day/month/year
  • timeout: duration Default: "200ms"

Stack

Properties

  • selected: int child index
  • transition: string animation name
  • transition_duration: int duration in millisecond as number

FlowBox

Properties

  • orientation: string layout direction
  • space_evenly: bool distribute children evenly
  • onaccept: string command on Enter (use {} for selected widget's name)
  • selection_model: string selection model

Graph

Properties

  • value: float current value
  • type: string graph type. possible values: "line" (default), "step-line", "fill", "step-fill"
  • time_range: duration the range of time to show. Default: "10s"
  • min: float minimum value
  • max: float maximum value
  • dynamic: bool whether the y range should dynamically change based on value. Default: true
  • animate: bool enable smooth scrolling. Default: true
  • flip_x: bool flip the graph horizontally. Default: false
  • flip_y: bool flip the graph horizontally. Default: false
  • vertical: bool if set to true, the x and y axes will be exchanged. Default: false
  • thickness: float the thickness of the line (for line charts). Default: 1.0
  • line_style: string changes the look of the edges in the graph (for line charts). possible values: "miter" (default), "bevel", "round"

Driving Widgets

This is how you update a widget after rendering in jscore. This is jscore's alternative to Poll and Listen. Jscore takes advantage of the superpower of the ewwii wc command and makes it the default way to update widgets through an API that abstracts all the complexity away.

Exposing a Widget

First, let's learn how a widget can qualify to be updated after rendering. It just simply has to have a widget_name property set.

let myBox = Widgets.Box().properties({
    widget_name: "cool-box"
});

That's it! ewwii wc can now find the widget, which this whole feature is based on.

Setup

To drive a widget render render, you simply need to define a function named after_render and export it.

export function after_render(api) {}

This function should take exactly one parameter, that is the API which you will use.

API

The api parameter that is passed to the after_render function only has one method. That is find. You can use the find method to find a widget with a specific widget_name and update it.

export function after_render(api) {
    const widget = api.find("cool-box");
}

Now the widget variable holds all the API's that can directly update the widget with the name cool-box. The widget variable holds these methods:

  • set_property
  • add_class
  • remove_class
  • add_classes
  • remove_classes

Signature of these methods:

set_property(string, string);

add_class(string);
remove_class(string);

// array of strings
add_classes([string]); 
remove_classes([string]);

Full Example

Here is an example that uses all the API's we've discussed.

export function after_render(api) {
    // suppose we have a widget 
    // with name 'cool-box'
    const widget = api.find("cool-box");

    widget.set_property("orientation", "h");

    widget.add_class("red");
    widget.add_classes("green", "blue", "orange");

    widget.remove_class("green");
    widget.remove_classes("red", "blue", "orange");
}

Do note that all of these runs all at once. Ideally, you'd use async combined with other functions to update widgets programmically. Jscore also has a stream namespace under Tools that provides predefined functions to make updating widgets easier.

Tools

Jscore exposes tools to help with you do most of the work from within JavaScript itself. You can imports the tools like so:

import * as Tools from "ewwii/tools";

stream

The stream namespace. This contains functions that can stream data to a closure. Useful alongisde after_render.

Methods:

All the methods are sync.

  • time

Example:

export function after_render(api) {
    const myLabel = api.find("cool-text");

    Tools.stream.time((t) => {
        console.log(`System ticked at: ${t.iso}`);
        myLabel.set_property("text", t.string);
    });
}

cmd

The command namespace. This contains all functions related to commands.

Methods:

All methods are async.

  • run(cmd)
  • run_read(cmd)

Example:

await Tools.cmd.run("notify-send Hi");

const output = await Tools.cmd.run_read("echo Hi");
console.log(output); // "Hi"

fs

The file system namespace. This contains functions that can modify/interact the file system.

Methods:

All methods are async.

  • read(path)
  • write(path, content)
  • append(path, contnet)
  • remove(path)
  • exists(path)
  • mkdir(path)
  • readdir(path)
  • stat(path)
  • copy(src, dest)
  • move(src, dest)

Example:

const path = "./example.js";

// Read to a variable
const contents_now = await Tools.fs.read(path);

// Replace contents with "Hello, World!"
await Tools.fs.write(path, "Hello, World!");

// Add previous content back below "Hello, World!"
await Tools.fs.append(path, contents_now);

// Now delete the file
await Tools.fs.remove(path);

// Check if it exists
if (Tools.fs.exists(path)) {
    // will not reach
    // as we removed it
}

// Make a directory
const directory = "mydir";
await Tools.fs.mkdir(directory);

// read all files (will be empty)
const files = await Tools.fs.readdir(directory);

// get info about the dir 
const info = await Tools.fs.stat(directory);
console.log(info);

// copy/move a dir/file 
await Tools.fs.copy(directory, "newdir");
await Tools.fs.move("newdir", "newdir_moved");

Builtins

Jscore is built on top of deno_core, which is a very minimal JavaScript evaluation engine which does not support things like timers, web, and more. To combat this, instead of using deno_runtime, which would make the binary size huge and increase the compile time by a lot, jscore implements most of these by itself instead.

fetch(url, {method, headers, body})

Supports:

  • ok
  • status
  • text()
  • json()

This is the simple fetch implementation of jscore. It is built for simple use cases and not for downloading large files as such. Simple actions like calling API's and extracting JSON will work seaminglessly.

Example:

// The second param is optional
const response = await fetch("https://api.github.com/repos/ewwii-sh/ewwii");
const json = await response.json();
console.log(json)

Timers

The timers like setInterval, setTimeout, etc. does not exist in deno_core. As these are simple functions, jscore has them built-in.

Example:

setTimeout example:

// Run a function after 2 seconds (2000ms)
const timeoutId = setTimeout(() => {
    console.log("This prints after 2 seconds!");
}, 2000);

// Cancel the timeout before it can execute
clearTimeout(timeoutId);

setInterval example:

let counter = 0;

// Run a function every 1 second (1000ms)
const intervalId = setInterval(() => {
    counter++;
    console.log(`Interval tick: ${counter}`);

    // Stop the loop after it runs 3 times
    if (counter === 3) {
        console.log("Stopping the interval loop.");
        clearInterval(intervalId);
    }
}, 1000);

Npm

Jscore can work seaminglessly with packages from node_modules/ with the help of esbuild. This feature not exclusive to npm. It works perfectly fine with pnpm or yarn because all of them use the same node_modules/ directory.

Directly trying to use a package similar to how you would in node wouldn't work in jscore. To use a package in jscore, you need to prefix esbuild: to the name of the package. This tells jscore to use esbuild to quickly convert the package into a standalone JavaScript file which it can easily load.

Example:

Here is an example on how to use lodash.

import lodash from 'esbuild:lodash';

const arr = [1, 2, 3, 4, 5];
console.log(lodash.chunk(arr, 2));
console.log(lodash.sum(arr));

Note:

Make sure that esbuild is installed before using this feature.

Technical Details

The packages that are bundled by esbuild are stored in the .cache/jscore directory to avoid bundling every time ewwii reloads jscore, which happens very frequently. Once in a while, its a good idea to clear the cache directory and let jscore repopulate it.

Packages that use node exclusive features like node:fs cannot be loaded by jscore. Although this limitation can be solved either by stubbing or redirecting to a simpler API, we decided keep it this way because no one will really needs node exclusive packages to build simple widgets.