npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@twined/brando

v1.0.0-alpha.5

Published

Brando node deps

Downloads

2

Readme

A helping hand.

Brando logo

Build Status Coverage Status Inline docs

EXPERIMENTAL, DO NOT USE

Install

Start by creating a new phoenix project:

$ mix phx.new my_project

Add Brando to deps in your mix.exs file:

defp deps do
  [{:brando, github: "twined/brando", branch: "develop"}]
end

Add Guardian conf to config/config.exs:

# Configure Guardian for auth.
config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "MyApp",
  ttl: {30, :days},
  verify_issuer: true, # optional
  secret_key: "SECRET_KEY. Create a new one with `mix phoenix.gen.secret`",
  serializer: Brando.GuardianSerializer

Fetch and compile dependencies. Install Brando:

$ mix do deps.get, deps.compile, brando.install

And then refetch new dependencies Brando has added to your mix.exs:

$ mix do deps.get, deps.compile

Add to your config/config.exs right before the env-specific import:

+ # Import Brando specific config.
+ import_config "brando.exs"
+
  # Import environment specific config. This must remain at the bottom
  # of this file so it overrides the configuration defined above.
  import_config "#{Mix.env}.exs"

Add to your relevant config/%{env}.exs Repo config:

  config :my_app, Repo,
+   types: MyApp.PostgresTypes

Install NPM packages:

$ cd assets && yarn

Set up database, and seed:

$ mix ecto.setup

Add to your config/prod.secret.exs (see https://github.com/elixir-lang/ecto/issues/1328)

  config :my_app, MyApp.Repo,
    adapter: Ecto.Adapters.Postgres,
    username: "my_app",
    password: "my_password",
    database: "my_app_prod",
    extensions: [{Postgrex.Extensions.JSON, library: Poison}],
+   socket_options: [recbuf: 8192, sndbuf: 8192],
    pool_size: 20

Go through config/brando.exs.

Static media config in endpoint.ex.

+ plug Plug.Static,
+  at: "/media", from: Brando.config(:media_path),
+  cache_control_for_etags: "public, max-age=31536000",
+  cache_control_for_vsn_requests: "public, max-age=31536000"

Also switch out (or add to it, if you use sockets in the frontend as well) the socket config in lib/web/endpoint.ex:

- socket "/socket", MyApp.Web.UserSocket
+ socket "/admin/ws", Brando.UserSocket

To use Brando's error view, add to your Endpoint's config (in prod.exs):

config :my_app, MyApp.Endpoint,
  render_errors: [accepts: ~w(html json), view: Brando.ErrorView, default_format: "html"],

Create a release configuration:

$ mix release.init

And set its config to default to prod.

Remember to switch out your ports and configure SSL in etc/supervisor/prod.conf and etc/nginx/prod.conf

Dependencies

  • imagemagick/mogrify for image processing.
  • gifsicle for GIF resizing.
  • pngquant for PNG optimization.
  • jpegtran for JPG optimization.

I18n

Brando uses Gettext for i18n.

To extract your frontend translations:

$ mix gettext.extract

Create your frontend translation directories: (for norwegian)

$ mkdir -p priv/gettext/frontend/nb/LC_MESSAGES

And backend:

$ mkdir -p priv/gettext/backend/nb/LC_MESSAGES

Merge frontend translations

$ mix gettext.merge priv/gettext/frontend

And backend:

$ mix gettext.merge priv/gettext/backend

Now we register our otp app's modules in Brando's registry to automatically set Gettext locales. Open up you application's lib/application.ex and add to start/2:

Brando.Registry.register(MyApp.Web, [:gettext])
Brando.Registry.register(MyApp.Web.Backend, [:gettext])

Extra modules

App specific modules

Generate templates:

$ mix brando.gen.html Task tasks name:string avatar:image data:villain

Also supports user:references to add a belongs_to assoc.

Copy outputted routes and add to lib/web/router.ex

Register your module in lib/application.ex:

    def start(_type, _args) do
      import Supervisor.Spec, warn: false

      children = [
        # Start the endpoint when the application starts
        supervisor(MyApp.Web.Endpoint, []),
        # Start the Ecto repository
        supervisor(MyApp.Repo, []),
        # Here you could define other workers and supervisors as children
        # worker(MyApp.Worker, [arg1, arg2, arg3]),
      ]

+     Brando.Registry.register(MyApp.Web.MyModule, [:menu])

Releases

Brando uses distillery through Docker for release management.

Start off by running

# mix release.init

Then use the fabric script in fabfile.py for the rest.

# fab prod -l

Additional admin CSS/styling

For modules added through your OTP app, you can style its backend by editing assets/css/custom/brando.custom.scss, or adding your own files to assets/css/custom/. Remember to include these from brando.custom.scss.

Additional admin Javascript

Add files to your assets/js/admin folder. These are compiled down to priv/static/js/brando.custom.js.

This file is included in the admin section's base template.

Pagination

For pagination, add to your app's repo.ex:

  defmodule MyApp.Repo do
    use Ecto.Repo, otp_app: :my_app
+   use Scrivener
  end

See Scrivener's docs for usage: https://hexdocs.pm/scrivener/

Default admin credentials

Default login/pass is [email protected]/admin

Sequence

Implements schema sequencing.

Controller:

  use Brando.Sequence,
    [:controller, [schema: MyApp.Schema,
                   filter: &MyApp.Schema.by_collection_id/1]]

The filter should return items by the :filter param in your routes.ex.

Example of a filter

def by_collection_id(id) do
  __MODULE__
  |> where([c], collection.id == ^id)
  |> order_by([c], c.sequence)
end

View:

  use Brando.Sequence, :view

Schema:

+ use Brando.Sequence, :schema

  schema "schema" do
    # ...
+   sequenced
  end

Migration:

+ use Brando.Sequence, :migration

  def up do
    create table(:schema) do
      # ...
+     sequenced
    end
  end

Template (sequence.html.eex):

<ul id="sequence" class="clearfix">
<%= for i <- @items do %>
  <li data-id="<%= i.id %>"><%= i.name %></li>
<% end %>
</ul>
<a id="sort-post" href="<%= Helpers.your_path(@conn, :sequence_post, @filter) %>" class="btn btn-default">
  Lagre rekkefølge
</a>

Add a link to the sequencer from your main template:

<a href="<%= Helpers.admin_your_path(@conn, :sequence, filter_id) %>" class="btn btn-default m-b-sm">
  <i class="fa fa-sort"></i> Sort
</a>

or add to tablize:

{"Sort this category", "fa-sort", :admin_your_path, :sequence, :id}

Finally, add to your routes (lib/web/router.ex):

  get    "/route/:filter/sorter", YourController, :sequence
  post   "/route/:filter/sorter", YourController, :sequence_post

Popup forms

First, register the form in your app's endpoint startup. The first argument is the name of the schema, second is the form module and third is a list of fields you want returned if repo insertion is successful:

Brando.PopupForm.Registry.register(:accounts, "client", MyApp.ClientForm, gettext("Create client"), [:id, :name])

import {PopupForm, brando} from "brando";

let params = [];
let initialValues = {email: '[email protected]'};
let clientForm = new PopupForm("accounts", brando.language, clientInsertionSuccess,
                               params, initialValues);

$('.avatar img').click((e) => {
    clientForm.show();
});

function clientInsertionSuccess(fields) {
    // here you'd insert the returned fields into a select or something similar.
    console.log(`${fields.id} --> ${fields.username}`);
}

Lockdown

If you want to limit the availability of your site while developing, you can use the Brando.Plug.Lockdown plug.

If you are authenticated, the website loads normally.

Example

    plug Brando.Plug.Lockdown, [
      layout: {MyApp.LockdownLayoutView, "lockdown.html"},
      view: {MyApp.LockdownView, "lockdown.html"}
    ]

Configure

    config :brando,
      lockdown: true,
      lockdown_password: "my_pass",
      lockdown_until: ~N[2016-08-02 19:00:00]

Password and time is optional.

If no password configuration is found, you have to login through the backend to see the frontend website.

HTML

To insert an expander:

<div class="expander">
  <a class="expander-trigger expander-hidden">Expandable section</a>
  <div class="expander-content">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio mollitia fugiat facilis enim accusamus quisquam aut, repellendus incidunt quod optio facere labore illo numquam ipsum beatae vero debitis, fugit excepturi.</p>
  </div>
</div>

Tags

Implements tagging in your schema.

Add to

Schema:

+ use Brando.Tag, :schema

  schema "schema" do
+   tags
  end

Migration:

+ use Brando.Tag, :migration

  def up do
    create table(:schema) do
+     tags
    end
  end

Page fragments

Example:

import Brando.Pages.Utils, only: [render_fragment: 2]

render_fragment("my/fragment", Gettext.get_locale(MyApp.Gettext)
render_fragment("my/fragment", "en")

If no language is passed, the default language set in brando.exs as default_language will be used.

If the fragment isn't found, it will render an error box.

Imagefield

A built in method for adding images to your schema is supplied for you.

In your schema:


+ use Brando.Field.ImageField

  schema "user" do
    field :username, :string
+   field :avatar, Brando.Type.Image
  end

+ has_image_field :avatar, %{
+   allowed_mimetypes: ["image/jpeg", "image/png", "image/gif"],
+   default_size: :medium,
+   upload_path: Path.join("images", "avatars"),
+   random_filename: true,
+   size_limit: 10_240_000,
+   sizes: %{
+     "micro"  => %{"size" => "25x25>", "quality" => 100, "crop" => true},
+     "thumb"  => %{"size" => "150x150>", "quality" => 100, "crop" => true},
+     "small"  => %{"size" => "300", "quality" => 100},
+     "medium" => %{"size" => "500", "quality" => 100},
+     "large"  => %{"size" => "700", "quality" => 100},
+     "xlarge" => %{"size" => "900", "quality" => 100}
+   }
+ }

The migration's field should be :text, not :string.

In your controller:

import Brando.Plug.Uploads
plug :check_for_uploads, {"user", Brando.User}
     when action in [:create, :profile_update, :update]

Villain

To use villain outside Brando.Pages and Brando.News, add to your app's lib/web/router.ex:

+ import Brando.Villain.Routes.Admin

  scope "/admin", as: :admin do
    pipe_through :admin
+   villain_routes "/whatever/has/villain", YourController
  end

Include js in whatever/_scripts.<action>.html.eex:

<%= Brando.Villain.HTML.include_scripts %>

Include css in whatever/_stylesheets.<action>.html.eex:

<link rel="stylesheet" href="<%= Helpers.static_path(@conn, "/css/villain.css") %>">

Initialize Villain in your template:

<%= Brando.Villain.HTML.initialize(
      base_url:     "/admin/news/",
      image_series: "news",
      source:       "textarea[name=\"post[data]\"]") %>

If you have custom blocks, add them in your config/brando.exs:

config :brando, Brando.Villain,
  extra_blocks: ["MyBlock", "AnotherBlock"]

Remember to add the image_series that Brando looks for.

You also need to call for parsing by invoking generate_html in your schema's changeset:

  def changeset(schema, params \\ %{}) do
    schema
    |> cast(params, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    |> generate_html()
  end

You can add separate parsers by supplying the parser module as a parameter to the generate_html function. If not, it will use the parser module given in config :brando, Brando.Villain, :parser.

See Brando.Villain help for more information on how to use in your project.

Brunch

Build for prod with brunch build --production.

Optimizing images

This requires you to have pngquant/jpegtran installed:

  config :brando, Brando.Images,
+   optimize: [
+     png: [
+       bin: "/usr/local/bin/pngquant",
+       args: "--speed 1 --force --output %{new_filename} -- %{filename}"
+     ],
+   jpeg: [
+     bin: "/usr/local/bin/jpegtran",
+     args: "-copy none -optimize -progressive -outfile %{new_filename} %{filename}"
+   ]

or

  config :brando, Brando.Images,
+   optimize: false

Deployment

Requires fabric.

Configure ./fabfile.py with your own values.

Make sure your local Docker machine is running, and that the env has been prepared:

$ docker-machine start default
$ eval "$(docker-machine env default)"

Ensure that @version "0.X.0" is set as module attribute in your mix.exs file.

Run

$ fab prod bootstrap_release

to deploy on your production box.

To build a new release

$ fab prod deploy_release

To seed your DB:

$ fab prod seed

To dump local db to .sql

$ fab dump_localdb

To upload local db to remote

$ fab prod upload_db

To load remote db

$ fab prod load_db

To dump/upload/load local db to remote:

$ fab prod dump_and_load_db

To upload your local media/ folder (only runs if remote media/ doesn't exists!)

$ fab prod upload_media

To upload your local etc/ folder

$ fab prod upload_etc