Designing

Designs are a central concept in the Hofstatder Studios system, and represent the source-of-truth to your application. Apps are groups of modules, modules are groups of types, and all have pages, components and customization. As you modify your designs, you push your application, and your live server will update to reflect the changes.

The next few Getting Started steps will walk you through some of the Hofstadter Studios concepts. We will use a note taking application as an example.

Application Folder

At the root of your application, you will see an important directory named design. This is the “design” of your application. You will see a number of files here, for configuring the app features, as well as a modules directory, where your modules will go.

The application directory overview:

<app-name>/  # your application directory
│
│   # Main application design
├── design/            # your design
├── design-vendor/     # imported modules
│
│   # Main non-design files
├── pages/             # for page content and layouts
├── translations/      # and multilingual assets
├── seeds/             # application seed data
├── custom/            # for custom files
├── components/        # for component content and layouts
│
│   # secrets are not uploaded with a push
│   # these are handled differently
├── secrets/     # for application secrets
│
│   # coming soon
└── funcs/       # serverless functions

Design Folder

Design represent the state you wish your application to be in. They are data files written in specific formats, called DSLs. While the examples are in yaml, you can intermix json, xml, toml, and hof-lang as well. Documentation for Designs are found in the reference section.

The design directory overview: (also design-vendor)

design
│
│   # Application Design
├── app.yaml
├── meta.yaml
├── config.yaml
├── users.yaml
├── auth.yaml
│
├── layout.yaml
├── pages.yaml
├── components.yaml
│
├── imports.yaml  # NPM imports
├── modules.yaml  # list of modules to enable
│
│
│   # Module and Type Files
└── modules/
   └─<module-name>
       │   # Module Design
       ├── module.yaml
       │
       │   # Type Design
       ├── <type-name-1>.yaml
       ├── <type-name-2>.yaml
       ├── ...
       │
       │   # Content and other files
       ├── pages/
       ├── components/
       ├── locales
       └── seeds/

We’ll get to know the various directories and files as we go through this page. More detailed information is available in the other major sections.

Changing the Title

We will start by making our own updates to the app, modules, types, pages, and components.

The first thing you may want to do is change the Title of your application. You can do this by editing:

design/app.yaml and changing the “title” field.

app:
  name: my-app
  title: "My Studios App"

Customizing the Home Page

Let’s change the home page to greet the user if logged in.

Update the design

You can find the design in design/pages.yaml

app:
  name: my-app

  pages:

    - name: home
      route: "/"
      style:
        - "pages/home/home.scss"
      content:
        - "pages/home/home.html"
      translations:
        - name: en
          file: pages/home/locales/en.json
        - name: es
          file: pages/home/locales/es.json

Add the following

      current-user: true
Update the content

change the content in pages/home/content.html from:

<div id="home-content">

<h1>{ t('title') }</h1>

<p>{ t('messages.hello') } from Hof Starter App</p>

</div>

to:

<div id="home-content">

<h1>{ t('messages.hello') }, { props.currentUser.username }</h1>

<p>Welcome to my first Studios app!</p>

</div>
What we did

In a few lines, we injected the current user into the home page and created a personalize geeting. You can also inject other data related to the user or create reusable components the same way.

What we didn’t do was create a user, setup an auth system or database, or link it to the front end. Hofstadter Studios handles all that for you.

You can focus on your application. Start with modules and types, create pages and components, declare the data and rules, design the layout and styling.

Creating New Functionality

There are several objects in Hofstadter Studios with which you can extend and customize your application:

After creating the application, you will:

The full new command options are in Studios Universe - Templates documentation.

Creating a Module

Modules group types and can include pages, components, translations, and other files. You can create a module by running:

hof new module
hof new module "design/modules/<module-name>"
module directory layout

This will create a skeleton of directories, designs, and other files:

design/modules/
└── <module-name>
    ├── module.yaml
    ├── pages/
    │   └── <page-name>/
    │       ├── content.jsx
    │       └── style.scss
    ├── components/
    │   └── <component-name>/
    │       ├── content.jsx
    │       └── style.scss
    ├── locales
    │   ├── en.json
    │   └── es.json
    └── seeds/
        └── default.json
module design file
module:
  name: ...

  types:
    - type-name-a
    - type-name-b

  pages: ...
  components: ...
  translations: ...
  seeds: ...
  files: ...
  ...
  # and more

Creating a Type

Types represent your application’s data or models, the fields, ownership, visibility, the relationships, permissions, rules, hooks, events, and more.

hof new type
hof new type "design/modules/<module-name>/<type-name>"
type directory layout

The new type command only creates a single file.

design/modules/
└── <module-name>
    │
    │   # The new file
    └── <type-name>.yaml
type design file
type:
  name: "<type-name>"

  fields: ...
    - name: ...
      type: ...
      ...

  relations: ...
    - name: ...
      type: type.modules.module-name.type-name
      relation: "one-to-one|one-to-many|many-to-many|..."

  owned:
    name: .. (optional)
    type: "has-one|has-many"
    ...

  lookup: ...
  visibility: ...

  auth:
    view: ...
    create: ...
    ...

  pages: ...
  components: ...
  translations: ...
  seeds: ...
  files: ...
  ...
  # and more

Pages and Components

New pages and components can appear at the app, module, or type level. You can substitue “component” for “page” in the subsections that follow.

hof new page

New Pages can appear under

# App page:
hof new page "design/<page-name>"

# Module page:
hof new page "design/modules/<module-name>/<page-name>"

# Type page:
hof new page "design/modules/<module-name>/<type-name>/<page-name>"
page directory layout
design/modules/
├── <module-name>
│   ├── <type-name>.yaml
│   │   │
│   │   │   # New Page Layout
└───└───└── pages
            ├── <page-name>.yaml
            └── <page-name>
                ├── content.html
                └── style.scss

page content design

The design file layout for pages and components:

app/module/type:
  name: ...

  pages:
    - name: <page-name>
      route: "/route/path"
      style:
        - design/path/to/style.scss
      content:
        - design/path/to/content.html

      imports: ...       # custom imports, pages only
      components: ...    # custom components
      translations: ...  # internationalization files

      # Inject data into the page or component
      #   the current user in the page data
      current-user: boolean
      data: ...

You can find the full format starting with the page snippet in the reference section

page data design

The current user and type data can be injected into pages and components. You can combine data from any type, local or across modules.

pages/components:
  - name: ...
    ...

    # Include the current, authenticated user information
    current-user: true

    # A list of type data to inject
    data:

        # The name will be accessigble from “props.name”
      - name: name-in-props

        # the dotpath to the type
        type: "type.modules.<module-name>.<type-name>"

        # Load data configuration
        query:
          # single object or a list
          type: "view" or "list"

          # whether the data should be real-time updated
          sync: true/false

          # ways to limit what data is sub selected or looked up
          filters: ...
          variables: ...

        # mutation functions accessible from "props.createTypeName”
        mutations:
          - create
          - update
          - delete

Full Reference

Note Taking Example Module

Let’s now create a notes module, type, and page. We’ll fill them in after learning how.

New module, type, and page
# create the module
hof new module design/modules/notes

# create the type
hof new type design/modules/notes/note

# create the page
hof new page design/modules/notes/board
Output folder layout
design/modules/notes/
│
├── module.yaml
├── note.yaml
│
├── pages
│   ├── board.yaml
│   └── board
│       ├── content.jsx
│       └── style.scss
├── components
│
├── locales
│   ├── en.json
│   └── es.json
└── seeds
    └── default.json
Wiring the app, modules, and types together

designs/modules.yaml

app:
  name: ...

  modules:
    - account
    - notes

designs/modules/notes/module.yaml

module:
  name: notes

  types:
    - note

Content, Data, and Mutations

Content is rendered from React Components. You can set the content and add styling. Both design configurations accept ordered lists so you can break your React code into multiple files.

Content JSX and SCSS

Page Content

The JSX returned from a React Component render(), make sure to have a single matching tag and to close all tags.

<div>
  ...
  // use data and mutations from the “props” object.
</div>
Component Content

Any new functions you want, you are in a React Component Class with ES6. Note, React component life cycle functions require calling the previous declaration.

Component is in a React Component Class:

// create functions here
...

render() {
  console.log(this.props)

  return (
    <div>
      ...
      // use data, mutations, and any functions
    </div>
  )
}

There are also ways to support full custom components, please see the other major sections of the documentation.

Layout and Styling Libraries

SCSS files

You can use SASS for styling.

https://sass-lang.com/

The files specified in the design list are injected in-order.

Bootstrap Framework

You can use anything from the Bootstrap Framework.

https://getbootstrap.com/docs/4.3/components/alerts/

Note, you have to substitute className when you see class because the content is JSX.

https://reactjs.org/docs/introducing-jsx.html

Custom Library Imports

You can import libraries from NPM to include in your application, please see the other major sections of the documentation.

Using Data and Mutations

The data and mutations you specifiy in your design will be attached to the props object in your content.

current-user

current-user is attached to the props object. You can find the included data using the following code.

<div>
  <h1>Current User: { props.currentUser.username }</h1>
  <pre>{ JSON.stringify(props.currentUser, '', '    ') }</pre>
</div>
data and loading

The data is attached by name to the props object as well. It also includes a loading property. The formats are dataName and loadingDataName, where data-name is the name you gave to the content data in the designs.

<div>
  { loadingNote ?
  <pre>loading...</pre>
  :
  <pre>{ JSON.stringify(props.note, '', '    ') }</pre>
  }
</div>
calling mutation functions

The data is attached by name to the props object as well. The format is mutationTypeName.

<div>
  <button className=”btn btn-danger” onClick={ () => props.deleteNote(props.note.id) }>delete</button>
  <hr />
  { loadingNote ?
  <pre>loading...</pre>
  :
  <pre>{ JSON.stringify(props.note, '', '    ') }</pre>
  }
</div>

Note Taking Example Content

We can now design our module, type, and page.

Design the Module

You shouldn’t have to actually do anything at this point.

design/modules/notes/module.yaml

module:

  name: notes

  types:
    - note

  components: []
  pages: []
  files: []

  translations:
  - name: en
    file: design/modules/notes/locales/en.json
  - name: es
    file: design/modules/notes/locales/es.json

  seeds:
    file: seeds/default.json
Design the Type

This is what the file should look like. We are setting the owned type and adding a content field.

design/modules/notes/module.yaml

type:
  name: note

  owned:
    type: has-many

  auth:
    default: true

  components:
    default: true

  pages:
    default: true

  fields:
  - name: name
    type: string
    length: 64
  - name: content
    type: text

  relations: []

  forms: []
  files: []
Design the Page

This is what the file should look like. We are setting the route to “/board”, adding the current user, and injecting the users note data.

design/modules/notes/pages/board.yaml

### DEV TODO, need to fix logic in TypeName and Pages/Components
# type: ...

module:
  name: board

  pages:
    - name: board
      route: "/board"


      style:
        - "design/modules/notes/pages/board/style.scss"

      content:
        - "design/modules/notes/pages/board/content.html"

      components: []

      current-user: true

      data:
        - name: notes
          type: type.module.notes.note
          query:
            type: list
            sync: true

We’ll make this a nice card base layout.

design/modules/notes/pages/board/content.html

<div className="container">
  <h1 className="row">{ props.currentUser.username }’s Notes</h1>
  <div className="row">
    <div className="col">
     { loadingNotes ? <pre>loading...</pre>
     : props.notes.map( (edge) => {
         let note = edge.note;

         return (
          <div key={ note.id } className="card" style={ { width: 18rem } }">
            <div className="card-body">
              <h5 className="card-title">{ note.name }</h5>
              <p className="card-text">{ note.content }</p>
              <a href={"/notes/edit/" + note.id} className="btn btn-primary">edit</a>
            </div>
          </div>
         )

       } )
     }
    </div>
  </div>
</div>

Custom Logic and Events

Serverless functions as well as built-in hooks, handlers and, integrations.

hof new func "<name>" "<template>"
hof func create "<name>" "<runtime>"
hof func "<command>" "<name>"

coming soon!

Custom and Private Repositories

The new command has a longer format enabling the use any git based repository or the local filesystem. See the Studios Universe - Templates documentation for more information.

Private repositories are supported for GitHub using the GITHUB_TOKEN environment variable.