Hi, I made UI for Nim, checkout Video Demo.
High productivity, simple and clean code are main priorities.
Checkout the Todo example or Video Demo.
Unline other UI frameworks it doesn't tied to any environment. The UI is just a function that get JSON string as input and respond with JSON string as output:
let out: seq[OutEvent] = component.process(events: seq[InEvent])
This function could be run as Nim Server or Compiled to JS and run in Browser, or Desktop App with WebView. Or even non-HTML renders like ImGui, although that would require some modifications.
Thanks for the advice about Karax DSL, there are some reasons I use h, I'm going to finish some app, and then see what templating going to be the best option, right now I'm not sure.
If someone would like to use Karax DSL it's possible, what matters is to return HtmlElement or seq[HtmlElement] object(s) from the component.render function, so any templating, or even explicitly built return HtmlElelement(tag: "div") would work.
Mono UI Web Framework got bunch of updates.
About templating system, it's a component framework, so it's supposed that you would create UI elements, and then use them to assemble your UI. Instead of joining bunch of HTML strings together. So, there should be almost no HTML code in your app.
The first part, creating elementary UI components, code below renders mockup for this UI.
proc PIconButton*(icon: string, title = "", size = "w-5 h-5", color = "bg-gray-500"): El =
let asset_root = if palette.mockup_mode: "" else: "/assets/palette/"
el"button .block.svg-icon":
it.class size & " " & color
unless title.is_empty: it.attr("title", title)
it.style fmt"-webkit-mask-image: url({asset_root}icons/" & icon & ".svg);"
...
code for more elementary components
...
let right = els:
el(PRBlock, ()):
el(PIconButton, (icon: "edit"))
el(PRBlock, ()):
el(PSearchField, ())
el(PFavorites, (links: data.links))
el(PTags, (tags: data.tags))
el(PSpaceInfo, (warns: @[("12 warns", "/warns")]))
el(PBacklinks, (links: data.links))
el(PRBlock, (title: "Other", closed: true))
el(PApp, (
title: "About Forex", title_controls: controls_stub,
tags: data.note_tags, tags_controls: controls_stub,
right: right
)):
el(PTextBlock, (html: data.text_block1_html))
el(PSection, (title: "Trends are down", tags: @["Finance", "Trading"]))
el(PTextBlock, (html: data.text_block2_html, controls: controls_stub))
el(PSection, (title: "Additional consequences of those 3 main issues", controls: controls_stub))
el(PListBlock, (html: data.list_block1_html, warns: @["Invalid tag #some", "Invalid link /some"]))
And then these elementary components used to build actual interactive UI. The component that renders the note (left part on the screenshot):
type FDocView* = ref object of Component
space*: Space
doc*: FDocHead
proc set_attrs*(self: FDocView, space: Space, doc: FDocHead) =
self.space = space; self.doc = doc
proc render*(self: FDocView): El =
let head = self.doc; let doc = self.doc.doc
let edit_title = edit_btn(doc.location)
let edit_tags = edit_btn(doc.location, doc.tags_line_n)
el(PApp, ( # App
title: doc.title, title_hint: doc.location, title_controls: @[edit_title],
tags: doc.tags, tags_controls: @[edit_tags]
)):
for warn in doc.warns: # Doc warns
el(PMessage, (text: warn, kind: PMessageKind.warn))
for section in doc.sections: # Sections
unless section.title.is_empty:
let edit_section = edit_btn(doc.location, section.line_n)
el(PSection, (title: section.title, tags: section.tags, controls: @[edit_section]))
for blk in section.blocks: # Blocks
add_or_return blk.render_fblock(doc, self.space, parent = self)
Out of curiosity since it is something I encounter a lot at work and have started valuing immensely, could I integrate this with Storybook ?
The reason for my question is that I discovered at work that design-systems have gained quite a lot of value, particularly for ensuring consistency and simplify communications between the designer in the team and the developer turning those designs into reality. Even independent of that I found it nice to have such an environment to be able to see my component and its behaviour in isolation.
Here a storybook example: https://philippmdoerner.github.io/nimstoryfont-gui/
Yes, storybooks are nice, yes it should be possible. For my use case full interactive storybook is overkill, and I use much simpler version, I call it Palette, basically I just render all UI elements in one page.
Here's couple of screenshots how it looks its Nim code, it also generates static HTML.
Having Palette and static HTML extremely useful, as you can iterate with UI design very fast without need to wait for slow Nim recompile. The changes to design are instant. And when you finish you backport changes to Nim.
Interactive File System Explorer in 20 lines of Nim and 20sec video demo
import base, mono/[core, http], std/os
type Ls = ref object of Component
path: string
proc render(self: Ls): El =
el"":
el("input", (autofocus: true, placeholder: "Path..."), it.bind_to(self.path))
let path = self.path
if not path.is_empty:
if fs.exist path:
if get_file_info(path).kind == pc_file:
el("pre", (text: fs.read(path)))
else:
for entry in fs.read_dir(path):
el("", (text: entry.name))
else:
el("", (text: fmt"File '{path}' doesn't exist"))
run_http_server(() => Ls())