WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

e280/lettuce

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation



Important

lettuce is just an early prototype.
more work is yet to be done in terms of features, extensibility, and customizability.



πŸ₯¬ lettuce

flexible layout ui for web apps

πŸ₯— splitty-panelly tabby draggy-droppy leafy layout ui

  • πŸ‘‰ https://lettuce.e280.org/ πŸ‘ˆ try it, nerd!
  • pane splitting, resizing, vertical, horizontal β€” you get it
  • dude, it's web components β€” universal compatibility
  • you can drag-and-drop tabs between panes
    • done efficiently with slots, tab doesn't remount to move
    • that's actually legit neato if you have heavy-weight stuff in your tabs
  • using

πŸ₯— what you're about to read

  • #quickstart β€” full install for lit apps
  • #layout β€” about the layout engine
  • #studio β€” about the ui systems
  • #react β€” react app compatibility



πŸ₯¬ quickstart your layout salad

how to setup lettuce in your lit app

πŸ₯— lettuce installation, html, and css

  1. install
    npm install @e280/lettuce lit
  2. html
    <lettuce-desk></lettuce-desk>
  3. css
    lettuce-desk {
      color: #fff8;
      background: #111;
    
      --scale: 1.5em;
      --gutter-size: 0.7em;
      --highlight: yellow;
      --special: aqua;
      --dropcover: 10%;
      --warn: red;
      --warntext: white;
      --dock: #181818;
      --taskbar: #181818;
      --tab: transparent;
      --tab-active: var(--dock);
      --gutter: #000;
      --focal: transparent;
      --pointerlock: yellow;
    }
  4. install shoelace into your html <head> (sorry)
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/themes/dark.css"
      onload="document.documentElement.classList.add('sl-theme-dark');"
    />
    <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/shoelace.js" ></script>

πŸ₯— lettuce typescript

  1. imports
    import {html} from "lit"
    import * as lettuce from "@e280/lettuce"
  2. setup your panels β€” these panels are available for the user to open
    const {panels, renderer} = lettuce.litSetup({
      alpha: {
        label: "Alpha",
        icon: () => html`A`,
        render: () => html`alpha content`,
        limit: 1, // optional max open instances
      },
      bravo: {
        label: "Bravo",
        icon: () => html`B`,
        render: () => html`bravo content`,
      },
      charlie: {
        label: "Charlie",
        icon: () => html`C`,
        render: () => html`charlie content`,
      },
    })
  3. setup your layout
    const layout = new lettuce.Layout({
      stock: lettuce.Builder.fn<keyof typeof panels>()(b => ({
        default: () => b.horizontal(1, b.dock(1, "alpha", "bravo", "charlie")),
        empty: () => b.blank(),
      })),
      defaultPanel: "alpha", // optional default panel for new splits
    })
    • panels are referenced by their string keys.
    • optional limit restricts how many copies of a panel can exist at the same time (default unlimited). once saturated, the adder buttons disable.
    • optional defaultPanel opens a default panel on new split docks (pick one that can open another instance).
    • Layout is a facility for reading and manipulating.
    • Builder.fn helps you build a tree of layout nodes with less verbosity (note the spooky-typing double-invocation).
    • stock.empty defines the fallback state for when a user closes everything.
    • stock.default defines the initial state for a first-time user.
  4. enable localstorage persistence (optional)
    const persistence = new lettuce.Persistence({
      layout,
      key: "lettuceLayoutBlueprint",
      kv: lettuce.Persistence.localStorageKv(),
      broadcastChannel: new BroadcastChannel("lettuceBroadcast"),
    })
    
    await persistence.load()
    persistence.setupAutoSave()
    persistence.setupLoadOnBroadcast()
    • see @e280/kv to learn how to control where the data is saved
  5. setup a studio for displaying the layout in browser
    const studio = new lettuce.Studio({
      panels,
      layout,
      renderer,
      // controls - optional
    })
    • controls uses standardControls(ctx) by default. Override it to render custom taskbar controls (see customize studio).
  6. register the web components to the dom
    studio.ui.registerComponents()



πŸ₯¬ layout

layout engine with serializable state

πŸ₯— layout package export path

  • import directly to avoid browser concerns (for running under node etc)
    import * as lettuce from "@e280/lettuce/layout"

πŸ₯— layout concepts explained

  • Blueprint
    • serializable layout data.
    • contains a version number and a root cell.
  • LayoutNode
    • any cell, dock, or surface.
    • all nodes have a unique string id.
    • all nodes have a kind string that is "cell", "dock", or "surface".
  • Cell
    • a cell is a group that arranges its children either vertically or horizontally.
    • this is where splits are expressed.
    • a cell's children can be docks or more cells.
  • Dock
    • a dock contains the ui with the little tab buttons, splitting buttons, x button, etc.
    • a dock's children must be surfaces.
    • each dock stores a taskbarAlignment ("top" | "right" | "bottom" | "left") which dictates where its taskbar renders and how the tabs orient themselves.
  • Surface
    • a surface is the rendering target location of where a panel will be rendered.
    • it uses a <slot> to magically render your panel into the location of this surface.

πŸ₯— layout explorer.ts β€” read and query immutable state

  • read the source code for the real details
  • the state that explorer returns is all immutable and readonly, if you try to mutate it, an error will be thrown
  • layout.explorer.root
  • layout.explorer.walk()
  • layout.explorer.all β€” is a "scout"
  • layout.explorer.cells β€” is a "scout"
  • layout.explorer.docks β€” is a "scout"
  • layout.explorer.surfaces β€” is a "scout"
  • all scouts have:
    • .getReport(id)
    • .requireReport(id)
    • .get(id)
    • .require(id)
    • .parent(id)
    • .reports
    • .nodes
    • .count

πŸ₯— layout actions.ts β€” mutate state

  • read the source code for the real details
  • these actions are the only way you can mutate or modify the state
  • layout.actions.mutate()
  • layout.actions.reset(cell?)
  • layout.actions.addSurface(dockId, panel)
  • layout.actions.activateSurface(surfaceId)
  • layout.actions.setDockActiveSurface(dockId, activeSurfaceIndex)
  • layout.actions.setDockTaskbarAlignment(dockId, alignment)
  • layout.actions.resize(id, size)
  • layout.actions.deleteSurface(id)
  • layout.actions.deleteDock(id)
  • layout.actions.splitDock(id, vertical)
  • layout.actions.moveSurface(id, dockId, destinationIndex)

πŸ₯— layout state management, using strata

  • get/set the data
    const blueprint = layout.getBlueprint()
    layout.setBlueprint(blueprint)
  • you can manually subscribe to changes like this
    layout.on(blueprint => {
      console.log("layout changed", blueprint)
    })
  • any strata-compatible ui (like sly) will magically auto-rerender
    import {view} from "@e280/sly"
    
    view(use => () => html`
      <p>node count: ${layout.explorer.all.count}</p>
    `)
  • you can use strata effects to magically respond to changes
    import {effect} from "@e280/strata"
    
    effect(() => {
      console.log("node count changed", layout.explorer.all.count)
    })



πŸ₯¬ studio

in-browser layout user-experience

πŸ₯— studio ui.ts β€” control how the ui is deployed

const studio = new lettuce.Studio({
  panels,
  layout,
  renderer,
  controls: context => {
    const standard = lettuce.standardControlsParts(context)
    return html`
      ${standard.spawnPanel()}
      ${standard.closeDock()}
      ${standard.splitHorizontal()}
      ${standard.splitVertical()}
      // customize non standard taskbar controls as you wish
      <button @click=${() => context.meta.studio.layout.actions.reset()}>Reset</button>
      // add your own action button
      <button @click=${() => someAction()}>whatever</button>
    `
  },
})
  • read the source code for the real details
  • standardControls(ctx) is the default taskbar controls (close, split, alignment, spawn panel, etc.).
  • import standardControlsParts instead when you need individual controls.
  • lettuce.listPanelsChoices(meta, dock) returns available panels for a dock, including icon, disabled state, and an open() handler.
  • studio.ui.registerComponents() β€” shortcut to register the components with their default names
  • studio.ui.views β€” access to ui in the form of sly views
    import {html} from "lit"
    
    html`
      <div>
        ${studio.ui.views.LettuceDesk()}
      </div>
    `
  • studio.ui.components β€” access to ui in the form of web components
    import {dom} from "@e280/sly"
    
    // manually registering the web components to the dom
    dom.register({
    
      // renaming the web component as an example
      LolDesk: studio.ui.components.LettuceDesk,
    })
    <lol-desk></lol-desk>



πŸ₯¬ react

lettuce for your react app

πŸ₯— install react and sly-react

  • sly-react allows you to turn any sly view into a react component
    npm install @e280/sly-react react

πŸ₯— setup your panels and layout

  • but this time, with jsx render fns
    const panels = {
      alpha: {
        label: "Alpha",
        icon: () => html`A`,
        render: () => <div>alpha content</div>, // πŸ‘ˆ jsx
      },
    }
    • note: your icons still have to be lit-html
  • and an ordinary layout
    const layout = new lettuce.Layout({
      stock: lettuce.Builder.fn<keyof typeof panels>()(b => ({
        default: () => b.horizontal(1, b.dock(1, "alpha")),
        empty: () => b.blank(),
      })),
    })

πŸ₯— make your studio and the hook

  • we literally provide sly-react's reactify and various react fns to reactIntegration
    import * as lettuce from "@e280/lettuce"
    import {reactify} from "@e280/sly-react"
    import {useRef, useState, useEffect, createElement} from "react"
    
    const {renderer, makeDeskComponent} = lettuce.reactIntegration({
      reactify,
      useRef,
      useState,
      useEffect,
      createElement,
    })
    
    const studio = new lettuce.Studio({renderer, panels, layout})
    const LettuceDesk = makeDeskComponent(studio)
    • lettuce does not depend on react, but accepts react-shaped stuff to perform the integration
    • studio requires the renderer that the react integration gives you

πŸ₯— LettuceDesk component usage

  • now you can use the component
    const MyReactComponent = () => {
      return (
        <div>
          <LettuceDesk render={surface => panels[surface.panel].render()} />
        </div>
      )
    }



πŸ₯¬ i made this open sourcedly just for you

pay your respects, gimmie a github star.

About

πŸ₯¬ incredible layout salad

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •