Giter VIP home page Giter VIP logo

neel's Introduction

Neel | HTML/JS GUI Library for Nim

Neel is a Nim library for making lightweight Electron-like HTML/JS GUI apps, with full access to Nim capabilities and targets any of the C, C++, or Objective-C backends.

By default, Neel opens a new Chrome session in app mode and allows the Nim backend and HTML/JS frontend to communicate via JSON and websockets. Alternate mode available, simply opening a new tab in your system's default browser. Webview capabilities coming soon.

Neel is designed to take all the hassle out of writing GUI applications. Current Features:

  • Eliminate 99% of boilerplate code
  • Automatic routes
  • Embeds frontend assets at compiletime (on release builds)
  • Automatic type conversions (from JSON to each procedure's parameter types)
  • Simple interface for backend/frontend communication
  • Cross-platform (tested on Mac, Windows, and Linux)

Neel is inspired by Eel, its Python cousin.


Introduction

Currently, Nim’s options for writing GUI applications are quite limited and if you wanted to use HTML/JS instead you can expect a lot of boilerplate code and headaches.

Neel is still in its infancy, so as of right now I don’t think it’s suitable for making full-blown commercial applications like Slack or Twitch. It is, however, very suitable for making all kinds of other projects and tools.

The best visualization libraries that exist are in Javascript and the most powerful capabilities of software can be harnessed with Nim. The goal of Neel is to combine the two languages and assist you in creating some killer applications.

Installation

Install from nimble: nimble install neel

Usage

Directory Structure

Currently, Neel applications consist of various static web assets (HTML,CSS,JS, etc.) and Nim modules.

All of the frontend files need to be placed within a single folder (they can be further divided into more folders within it if necessary). The starting URL must be named index.html and placed within the root of the web folder.

main.nim            <----\
database.nim        <-------- Nim files
other.nim           <----/
/assets/             <------- Web folder containing frontend files (can be named whatever you want)
  index.html        <-------- The starting URL for the application (**must** be named 'index.html' and located within the root of the web folder)
  /css/
    style.css
  /js/
    main.js

Developing the Application

Nim / Backend

We begin with a very simple example, from there I'll explain the process and each part in detail.

(main.nim)

import neel #1

exposeProcs: #2
    proc echoThis(jsMsg: string) =
        echo "got this from frontend: " & jsMsg
        callJs("logThis", "Hello from Nim!") #3

startApp(webDirPath="path-to-web-assets-folder") #4
#1 import neel

When you import neel, several modules are exported into the calling module. exposedProcs and startApp are macros that require these modules in order to work properly. The list below are all of the exported modules. It's not necessary to remember them, and even if you accidently imported the module twice Nim disregards it. This is just for your reference really.

  • std/os
  • std/osproc
  • std/strutils
  • std/json
  • std/threadpool
  • std/browsers
  • std/jsonutils
  • pkg/mummy
  • pkg/routers
#2 exposeProcs

exposeProcs is a macro that exposes specific procedures for javascript to be able to call from the frontend. When the macro is expanded, it creates a procedure callNim which contains all exposed procedures and will call a specified procedure based on frontend data, passing in the appropriate parameters (should there be any).

The data being received is initially JSON and needs to be converted into the appropriate types for each parameter in a procedure. This is also handled by the macro.

As of Neel 1.1.0, you can use virtually any Nim type for parameters in exposed procedures. Neel uses std/jsonutils to programmatically handle the conversions. Some caveats:

  • Does not support default values for parameters.
  • Does not support generics for parameters.

This above macro produces this result:

proc callNim(procName: string; params: seq[JsonNode]) =
    proc echoThis(jsMsg: string) =
        echo "got this from frontend: " & jsMsg
        callJs("logThis", "Hello from Nim!")
    case procName
    of "echoThis": echoThis(params[0].getStr)
    ...

NOTE: You can pass complex data in a single parameter now if you'd like to. Use Javascript objects or dictionaries and simply create a custom object type to accept it from the Nim side.

I'm sure this is obvious, but it's much cleaner to have your exposed procedures call procedures from other modules.

Example:

# (main.nim)
import neel, othermodule
exposeProcs:
    proc proc1(param: seq[JsonNode]) =
        doStuff(param[0].getInt)
...

# (othermodule.nim)
from neel import callJs # you only need to import this macro from Neel :)

proc doStuff*(param: int) =
    var dataForFrontEnd = param + 100
    callJs("myJavascriptFunc", dataForFrontEnd)
...
#3 callJs

callJs is a macro that takes in at least one value, a string, and it's the name of the javascript function you want to call. Any other value will be passed into that javascript function call on the frontend. You may pass in any amount to satisfy your function parameters needs like so:

callJs("myJavascriptFunc",1,3.14,["some stuff",1,9000])

The above code gets converted into stringified JSON and sent to the frontend via websocket.

#4 startApp

startApp is a macro that handles server logic, routing, and Chrome web browser. startApp currently takes 6 parameters. example:

startApp(webDirPath= "path_to_web_assets_folder", portNo=5000,
            position= [500,150], size= [600,600], chromeFlags= @["--force-dark-mode"], appMode= true)
            # left, top          # width, height
  • webDirPath : sets the path to the web directory containing all frontend assets needs to be set
  • portNo : specifies the port for serving your application (default is 5000)
  • position : positions the top and left side of your application window (default is 500 x 150)
  • size : sets the size of your application window by width and height(default is 600 x 600)
  • chromeFlags : passes any additional flags to chrome (default is none)
  • appMode : if "true" (default) Chrome will open a new session/window in App mode, if "false" a new tab will be opened in your current default browser - which can be very useful for debugging.

Javascript / Frontend

The Javascript aspect of a Neel app isn't nearly as complex. Let's build the frontend for the example above:

(index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Neel App Example</title>
    <script src="/neel.js"></script> <!-- always include /neel.js in your <head> -->
</head>
<body>
    <h1>My First Neel App</h1>
    <script src="/main.js"></script> <!-- always use absolute paths with web assets -->
</body>
</html>

(main.js)

neel.callNim("echoThis","Hello from Javascript!")

function logThis(param){
    console.log(param)
}

The first thing you'll notice is we've included a script tag containing /neel.js in the section of our HTML page. This allows Neel to handle all of the logic on the frontend for websocket connections and function/procedure calls.

neel.callNim is a function that takes in at least one value, a string, and it's the name of the Nim procedure you want to call. Any other value will be passed into that Nim procedure call on the backend asa parameter. You must pass in the correct number of params for that proc, in order, and of the correct types for it to be called properly. Example:

frontend call to backend:

neel.callNim("myNimProc",1,3.14,["some stuff",1,9000])

must match the result of the exposeProcs macro:

...
of "myNimProc": return myNimProc(params[0].getInt,params[1].getFloat,params[2].getElems)
...

As of Neel 1.1.0, there is exception handling in place (with a verbose logging in debug builds) for unknown procedure calls and parameter type mismatches.

Going back to our first example, when index.html is served, Javascript will call the echoThis procedure and pass "Hello from Javascript!" as the param. This will print the string in the terminal. Then, Nim will call the logThis function and pass "Hello from Nim!". Neel handles the JSON conversion, calls the function and passes in the param.

Now open the console in Chrome developer tools and you should see "Hello from Nim!".

Keep In Mind: absolute paths must be used within your HTML files ex: <script src="/this_js_module.js></script>

Compilation Step

If using nim 1.6.X branch, compile your Neel application with --threads:on and --mm:orc. Nim >= 2.0.0 does this by default. example:

nim c -r --threads:on --mm:orc main.nim

When compiling for Windows, also pass the --app:gui flag on your release builds if you want to prevent the app opening up with a terminal. example:

nim c -r --threads:on --mm:orc --app:gui -d:release main.nim

Final Thoughts Before Developing With Neel

Keep the following in mind when developing you Neel App:

  • All of your frontend assets are embedded into the binary when compiling a release build. Stick to debug builds when needing to modify/change static frontend assets, as you can simply refresh a page and see the updates in real-time.
  • To prevent crashes when users spam refresh or constantly switch between different pages, we implemented a sort of countdown timer for shutting down. For debug builds, approximately 3 seconds after closing the app window the server and program is shutdown if a websocket hasn't reconnected within that time period. For release builds that time delay is approximately 10 seconds for some extra cusion.

Examples

A simple Neel app that picks a random filename out of a given folder (something impossible from a browser):

Random File Picker

Neel version 1.0.0 Newly Released 10/28/23

Neel now leverages the power of Mummy for websever / websocket needs. Mummy returns to the ancient way of threads, removing the need for async entirely. Historically Neel applications ran into async problems in certain situations. For example, try/except blocks were necessary in some exposed procedures yet using async procedures within an except block is a no go.

Future Work

The vision for this library is to eventually have this as full-fledged as Electron for Nim. I believe it has the potential for developing commercial applications and maybe one day even rival Electron as a framework.

A BIG teaser for what's to come within the next few releases:

  • Arbitrary JavaScript-To-Nim Type Conversions (JSON Parsing)

    As long as the parameter types from the Javascript side match to the Nim side, use what you want!
    
  • Webview Capabilities

    [Webview](https://github.com/webview/webview) is an MIT licensed cross-platform webview library for C/C++. Uses WebKit (GTK/Cocoa) and Edge WebView2 (Windows). Having a webview target will get us closer to solid framework for shipping commercial builds.
    
  • Distributable Applications

    Build your Neel app and have it packaged and ready to be shipped.
    

neel's People

Contributors

c-nerd avatar konradmb avatar mantielero avatar narimiran avatar necroticooze avatar niminem avatar trolley33 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

neel's Issues

Windows build does not works standalone

  1. I've compiled the app on windows 10 using MinGW x64
  2. After I copied and run the app on other device - it require libwinpthread-1.dll
  3. I copied the lib from my workspace
  4. Then the app started with 0xc000007b crash and exits

Curiosity

Every now and then I glance at this project . the concept seems very interesting , but I see that the project is at a standstill . Has the development been stopped ?

Application doesn't start if not on the same drive letter as Chrome

Environment
Nim 1.4.2 / Windows 10 ver. 2004 / Neel 0.2.7

Problem
My home and work PCs have two drives (C: is an SSD, D: is an HDD). If I run a Nim application that uses Neel on the drive that has Chrome installed, it runs. If I try it from the other drive, it fails.

i.e. from C:\temp\blah.exe - the executable runs (since Chrome is installed on the C: drive)
from D:\temp\blah.exe - I get the following error in a dialog box:

neel.nim(182) findChromeWindows
Error: unhandled exception: could not find Chrome in Program Files (x86) directory [CustomError]

I suspect the logic for detecting Chrome's install location in Neel needs to be beefed up.

Possible solution:
It may be possible to check the Windows registry to find the Chrome install location if the current logic can't find the exe. A quick check of Stack Overflow suggested the following registry key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe

I don't know if this registry key is always there/is the 'official' one but it exists on my Windows 10 install and points to the Chrome install folder.

Also, thanks for creating Neel! I've tried other Nim webview components and yours was the first that let me complete a small utility I needed.

Cannot install neel.

I get this error:

Downloading https://github.com/Niminem/Neel using git
  Verifying dependencies for [email protected]
      Info: Dependency on jester@>= 0.5.0 already satisfied
  Verifying dependencies for [email protected]
      Info: Dependency on httpbeast@>= 0.2.2 already satisfied
  Verifying dependencies for [email protected]
      Info: Dependency on asynctools@#0e6bdc3ed5bae8c7cc9 already satisfied
  Verifying dependencies for asynctools@#0e6bdc3ed5bae8c7cc9
      Info: Dependency on https://github.com/timotheecour/asynctools@#pr_fix_compilation already satisfied
  Verifying dependencies for asynctools@#pr_fix_compilation
      Info: Dependency on ws@>= 0.4.2 already satisfied
  Verifying dependencies for [email protected]
 Installing [email protected]
   Building neel/neel using c backend
�[1m/tmp/nimble_1130/githubcom_NiminemNeel/src/neel.nim(16, 37) �[0m�[31mError: �[0mcannot open file: /tmp/nimble_1130/githubcom_NiminemNeel/src/assets/index.html�[36m�[0m
    Prompt: Build failed for '[email protected]', would you like to try installing 'neel@#head' (latest unstable)? [y/N]

Nimble is apparently running your module when installing, please fix this.
UPDATE:
Got this error on linux and windows

[Question] Using Neel for a web application?

Hi,

Is it possible for Neel be used for a web application?
I would like to make a single executable web application I've seen done in the Go programming language,
and was wondering if Neel could be a candidate for these kinds of applications in Nim.

Thanks
Matic

Contributing to the project

Hi great project you have here I am new to github in general and I want to help contribute to this project. Do you have any new features that needs implementation if so I would love to help implement them.

filePicker example does not compile

Hi,

Trying to compile the filePicker.nim give the following error:

C:\Users\someuser\.nimble\pkgs\neel-0.2.7\neel.nim(6, 20) Warning: inherit from a more precise exception type like ValueError, IOError or OSError. If these don't suit, inherit from
 CatchableError or Defect. [InheritFromException]
C:\Users\someuser\.nimble\pkgs\jester-0.5.0\jester.nim(1298, 9) Hint: Asynchronous route: theRouter. [User]
C:\Users\someuser\Desktop\Neel-master\examples\filePicker.nim(21, 9) template/generic instantiation of `startApp` from here
C:\Users\someuser\.nimble\pkgs\neel-0.2.7\neel.nim(241, 16) template/generic instantiation of `router` from here
C:\Users\someuser\.nimble\pkgs\jester-0.5.0\jester.nim(1304, 35) template/generic instantiation of `async` from here
C:\Users\someuser\.nimble\pkgs\neel-0.2.7\neel.nim(292, 88) Error: type mismatch: got <string, proc (req: Request): string{.noSideEffect, gcsafe, locks: 0.}>
but expected one of:
proc `/`(head, tail: string): string
  first type mismatch at position: 2
  required type for tail: string
  but expression 'path' is of type: proc (req: Request): string{.noSideEffect, gcsafe, locks: 0.}
3 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them

expression: getCurrentDir() / "web" / path

Any ideas on how to solve this?
Specs:

  • Windows 10
  • Nim Devel

Example crash

Hi, thx for the nice lib.
I am using version 0.4.0 + nim 1.4.8 and noticed that example app (also my app) can crash when the page is reloaded frequently.
Either this can be checked in the browser by spamming the F5 button.

Calls to frontend are too slow/blocking

Hi @Niminem , thanks for the work, it was quite easy to get started with neel.

I have a problem though. The communication from backend to frontend seems to be very slow (or something that should not be blocking is blocking)

I have created tried to create a simple paint on canvas app, that catches the mouse move event on the frontend, sends the coordinates to backend, which prints them out and tells front end to paint them on the canvas. (Obviously redundant, just for the sake of the example.)

The coordinates are printed out immediately, when I comment out the call to frontend, with the call in place, large delays occur (multiple seconds). To make sure it is not the function itself, I have replaced it with an empty function on the front end side, and it had the same effect.

Cross-compilation from Linux to Windows

Hi!

First of all, thank you so much for this amazing project! It has clearly encouraged me to start using Nim to create simple and shareable apps (still a beginner). Combining Nim's performance with a nice frontend in HTML/CSS/JS is a fantastic idea.

I have successfully created my first app on Linux, which works well. I am still figuring out how to bind C header files and shared libraries to the executable, but that's another subject. Now, I wanted to inquire if there is support for cross-compilation from Linux to Windows using Nim. According to the Nim Compiler User Guide, I tried to compile my app into a Windows executable using the following command:

nim c -d:mingw --threads:on --mm:orc --app:gui -d:release main.nim

This command successfully creates a main.exe binary. However, when I try to run it on Windows, I encounter a black screen with the following error message (translated from French):

"This page does not work
Impossible to process this request via localhost at this time.
HTTP ERROR 500"

I have also attempted to compile the "filepicker" example, which works perfectly when compiled for Linux but encounters the same problem when compiled for Windows. Am I doing something wrong, or is cross-compilation not supported (yet)?

Thanks in advance

any kind of error in callJs leads to JSON parse error, original error disappears

The code in neel.js (see neel.nim, line 254) for responding to a callJs request will swallow any exception caused by the function being called and make it look like the problem is a JSON parse error. Offending code section:

254 ws.onmessage = (data) => {
255     try {
256         let x = JSON.parse(data.data)
257         let v = Object.values(x)
258         neel.callJs(v[0], v[1])
259     } catch (err) {
260         let x = JSON.parse(data)
261         let v = Object.values(x)
262         neel.callJs(v[0], v[1])
263     }
264 }

If line 256 succeeds and the function called in line 258 then throws an exception, then the catch block attempts to re-parse the whole message as JSON, which fails with Uncaught SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data and the original exception which was thrown in the try block is lost.

I think this would do the trick:

254 ws.onmessage = (data) => {
255    let x;
256     try {
257         x = JSON.parse(data.data)
258     } catch (err) {
259         x = JSON.parse(data)
260     }
261         let v = Object.values(x)
262         neel.callJs(v[0], v[1])
263 }

CallJs in try/except Causes Segfault

If an exposeProc uses try/except and then calls callJs from within the except block, the app crashes with a segfault.

Try this:

exposeProcs:
  proc tryExcept() =
    try:
      raise newException(CatchableError, "Oh no!")
    except:
      callJs("reportException", "except")

I'm not sure this is an issue with Neel so much as with Nim itself, but I figured it'd be better to point it out. What's going on here and how does one handle this?

MIME type

INFO Jester is making jokes at http://127.0.0.1:5000 (all interfaces)
DEBUG GET /
DEBUG   200 OK {"cache-control": @["no-store"]}
DEBUG GET /main.css
DEBUG   200 OK {"cache-control": @["no-store"]}
DEBUG GET /three.module.js
DEBUG   200 OK {"cache-control": @["no-store"]}
DEBUG GET /GLTFLoader.js
DEBUG   200 OK {"cache-control": @["no-store"]}
DEBUG GET /RoomEnvironment.js
DEBUG   200 OK {"cache-control": @["no-store"]}
DEBUG GET /lil-gui.module.min.js
DEBUG   200 OK {"cache-control": @["no-store"]}

JS:

		import * as THREE from './three.module.js';
		import { GLTFLoader } from './GLTFLoader.js';
		import { RoomEnvironment } from './RoomEnvironment.js';
		import { GUI } from './lil-gui.module.min';

Chrome:



GLTFLoader.js:1
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
RoomEnvironment.js:1
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
lil-gui.module.min.js:1
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.
three.module.js:1
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.

As far as I understand, I need to set the MIME type on the server, how can I do this? I use three.js .

CrossBrowdy And GraBulma Support?

I just started checking out these projects, but I'm thinking they might make a killer Nim project if put together. I'm thinking GraBulma and Nim would be good for building CrossBrowdy modules, and then using Neel to ship the clients? I'm going to check with the other developers to see what they think, but I thought I'd start here.
I'm having a problem which I'll file a separate issue for, but I'm not getting any pages to show up in Neel. I'm also not seeing any errors.
I know it's still early in the development cycle, but I'd thought I'd just check. It might be because I'm using a Mac.
https://crossbrowdy.com
https://github.com/juancarlospaco/grabulma

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.