Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ui #52

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft

Ui #52

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ locals_without_parens = [
a: :*,
abbr: :*,
action: :*,
action: :*,
address: :*,
all: :*,
all: :*,
Expand Down Expand Up @@ -92,6 +93,7 @@ locals_without_parens = [
not_nil: :*,
object: :*,
ol: :*,
on: :*,
one: :*,
one: :*,
optgroup: :*,
Expand Down
5 changes: 3 additions & 2 deletions lib/sleeky.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ defmodule Sleeky do
Sleeky.Context.Dsl,
Sleeky.JsonApi.Dsl,
Sleeky.Endpoint.Dsl,
Sleeky.View.Dsl,
Sleeky.Ui.Dsl
Sleeky.Ui.Dsl,
Sleeky.Ui.Action.Dsl,
Sleeky.Ui.View.Dsl
]

@doc """
Expand Down
32 changes: 31 additions & 1 deletion lib/sleeky/context/generator/create_actions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ defmodule Sleeky.Context.Generator.CreateActions do

@impl true
def generate(context, _) do
create_funs(context) ++ do_create_funs(context) ++ create_children_funs(context)
create_funs(context) ++
do_create_funs(context) ++ bulk_create_funs(context) ++ create_children_funs(context)
end

defp create_funs(context) do
Expand Down Expand Up @@ -123,4 +124,33 @@ defmodule Sleeky.Context.Generator.CreateActions do
end
end
end

defp bulk_create_funs(context) do
for model <- context.models, %{name: :create} <- model.actions() do
single_fun_name = String.to_atom("create_#{model.name()}")
bulk_fun_name = String.to_atom("create_#{model.plural()}")

quote do
def unquote(bulk_fun_name)(items, context \\ %{}) when is_list(items) do
repo = repo()

with {:ok, :ok} <-
repo.transaction(fn ->
items
|> Enum.reduce_while(nil, fn item, _ ->
case unquote(single_fun_name)(item, context) do
{:ok, _} -> {:cont, :ok}
{:error, _} = error -> {:halt, error}
end
end)
|> then(fn
:ok -> :ok
{:error, reason} -> repo.rollback(reason)
end)
end),
do: :ok
end
end
end
end
end
6 changes: 5 additions & 1 deletion lib/sleeky/job.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ defmodule Sleeky.Job do
model: model,
id: id,
queue: job.queue,
job: job.id,
reason: format_error(reason),
attempt: job.attempt,
attempts_left: attempts_left
)
end
Expand All @@ -92,7 +94,9 @@ defmodule Sleeky.Job do
task: task,
model: model,
id: id,
queue: job.queue
job: job.id,
queue: job.queue,
attempt: job.attempt
)
end

Expand Down
30 changes: 0 additions & 30 deletions lib/sleeky/template.ex

This file was deleted.

5 changes: 3 additions & 2 deletions lib/sleeky/ui.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ defmodule Sleeky.Ui do
@moduledoc false
use Diesel,
otp_app: :sleeky,
dsl: Sleeky.Ui.Dsl,
generators: [
Sleeky.Ui.Generator.Router
]

defstruct [:pages]
defstruct [:pages, :error_view, :not_found_view]

defmodule Page do
@moduledoc false
defstruct [:path, :module]
defstruct [:method, :path, :module, :runtime]
end
end
1 change: 1 addition & 0 deletions lib/sleeky/ui/dsl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule Sleeky.Ui.Dsl do
@moduledoc false
use Diesel.Dsl,
otp_app: :sleeky,
root: Sleeky.Ui.Dsl.Ui,
tags: [
Sleeky.Ui.Dsl.Page
]
Expand Down
9 changes: 8 additions & 1 deletion lib/sleeky/ui/dsl/page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ defmodule Sleeky.Ui.Dsl.Page do
use Diesel.Tag

tag do
child kind: :module, min: 1, max: 1
attribute :name, kind: :module
attribute :at, kind: :string

attribute :method,
kind: :atom,
in: [:get, :post, :put, :delete],
default: :get,
required: false
end
end
2 changes: 1 addition & 1 deletion lib/sleeky/ui/dsl/ui.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ defmodule Sleeky.Ui.Dsl.Ui do
use Diesel.Tag

tag do
child :page, min: 0
child :page, min: 1
end
end
39 changes: 29 additions & 10 deletions lib/sleeky/ui/generator/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,7 @@ defmodule Sleeky.Ui.Generator.Router do

conn = var(:conn)

routes =
for {path, page} <- ui.pages do
html = page.render()

quote do
get unquote(path) do
send_html(unquote(conn), unquote(html), 200)
end
end
end
routes = Enum.map(ui.pages, &route(ui, &1))

quote do
defmodule unquote(module_name) do
Expand All @@ -47,4 +38,32 @@ defmodule Sleeky.Ui.Generator.Router do
end
end
end

defp route(ui, page) do
conn = var(:conn)
not_found_view = ui.not_found_view
error_view = ui.error_view

quote do
match unquote(page.path), via: unquote(page.method) do
case unquote(page.module).data(unquote(conn).params) do
{:ok, data} ->
html = unquote(page.module).render(data)
send_html(unquote(conn), html, 200)

{:ok, :redirect, path} ->
conn = put_resp_header(unquote(conn), "location", path)
send_resp(conn, 302, "")

{:error, :not_found} ->
html = unquote(not_found_view).render(unquote(conn).params)
send_html(unquote(conn), html, 200)

{:error, other} ->
html = unquote(error_view).render(unquote(conn).params)
send_html(unquote(conn), html, 200)
end
end
end
end
end
35 changes: 16 additions & 19 deletions lib/sleeky/ui/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,28 @@ defmodule Sleeky.Ui.Parser do
@behaviour Diesel.Parser

alias Sleeky.Ui
alias Sleeky.Ui.Page

@impl true
def parse({_, _, children}, opts) do
caller = opts[:caller_module]
def parse({:ui, _, pages}, opts) do
ui = Keyword.fetch!(opts, :caller_module)

pages =
for {:page, _, [module]} <- children, into: %{} do
{path(caller, module), module}
end

%Ui{pages: pages}
end
for {:page, page, _} <- pages do
path = Keyword.fetch!(page, :at)
module = Keyword.fetch!(page, :name)
method = Keyword.get(page, :method, :get)

defp path(caller, module) do
caller = Module.split(caller)
%Page{
method: method,
path: path,
module: module
}
end

path =
module
|> Module.split()
|> Kernel.--(caller)
|> Enum.map(&Inflex.parameterize/1)
|> Enum.reject(&(&1 == "index"))
|> Enum.join("/")
|> String.replace("//", "/")
error_view = Module.concat(ui, Error)
not_found_view = Module.concat(ui, NotFound)

"/" <> path
%Ui{pages: pages, error_view: error_view, not_found_view: not_found_view}
end
end
11 changes: 11 additions & 0 deletions lib/sleeky/ui/view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Sleeky.Ui.View do
@moduledoc false
use Diesel,
otp_app: :sleeky,
overrides: [div: 2],
generators: [
Sleeky.Ui.View.Generator.Render,
Sleeky.Ui.View.Generator.Source,
Sleeky.Ui.View.Generator.Data
]
end
4 changes: 2 additions & 2 deletions lib/sleeky/view/dsl.ex → lib/sleeky/ui/view/dsl.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Sleeky.View.Dsl do
defmodule Sleeky.Ui.View.Dsl do
@moduledoc false
use Diesel.Dsl,
otp_app: :sleeky,
Expand Down Expand Up @@ -116,7 +116,7 @@ defmodule Sleeky.View.Dsl do
:video,
:wbr,
# custom extensions
:using,
:layout,
:slot,
:each,
:expand
Expand Down
13 changes: 13 additions & 0 deletions lib/sleeky/ui/view/generator/data.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Sleeky.Ui.View.Generator.Data do
@moduledoc false
@behaviour Diesel.Generator

@impl true
def generate(_html, _opts) do
quote do
def data(params), do: {:ok, params}

defoverridable data: 1
end
end
end
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
defmodule Sleeky.View.Generator.Render do
defmodule Sleeky.Ui.View.Generator.Render do
@moduledoc false
@behaviour Diesel.Generator

alias Sleeky.View
alias Sleeky.Ui.View

@impl true
def generate(html, _opts) do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Sleeky.View.Generator.Source do
defmodule Sleeky.Ui.View.Generator.Source do
@moduledoc false
@behaviour Diesel.Generator

Expand Down
2 changes: 1 addition & 1 deletion lib/sleeky/view/parser.ex → lib/sleeky/ui/view/parser.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Sleeky.View.Parser do
defmodule Sleeky.Ui.View.Parser do
@moduledoc false
@behaviour Diesel.Parser

Expand Down
4 changes: 2 additions & 2 deletions lib/sleeky/view/render.ex → lib/sleeky/ui/view/render.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Sleeky.View.Render do
defmodule Sleeky.Ui.View.Render do
@moduledoc false

@self_closing_tags [
Expand Down Expand Up @@ -55,7 +55,7 @@ defmodule Sleeky.View.Render do
" #{Enum.map_join(attrs, " ", &do_attr/1)}"
end

defp do_attr({name, _}) when name in [:defer] do
defp do_attr({name, true}) do
"#{name}"
end

Expand Down
48 changes: 48 additions & 0 deletions lib/sleeky/ui/view/resolve.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule Sleeky.Ui.View.Resolve do
@moduledoc false

def resolve({:layout, [name: layout], children}, slots) do
more_slots =
for {:slot, [name: name], value} <- children, into: %{} do
value =
case value do
[component] when is_atom(component) -> component
values when is_list(values) -> values
end

{name, value}
end

slots = Map.merge(slots, more_slots)

layout.source() |> resolve(slots)
end

def resolve({:slot, _, [name]}, slots) do
case Map.get(slots, name) do
nil ->
{:div, [], []}

component when is_atom(component) ->
component.source() |> resolve(slots)

nodes when is_list(nodes) ->
resolve(nodes, slots)
end
end

def resolve({:each, attrs, [{_, _, _} = child]}, slots) do
child = resolve(child, slots)
{:each, attrs, [child]}
end

def resolve({:each, attrs, [component]}, slots) when is_atom(component),
do: resolve({:each, attrs, [component.source()]}, slots)

def resolve({tag, attrs, children}, slots),
do: {tag, attrs, Enum.map(children, &resolve(&1, slots))}

def resolve(nodes, slots) when is_list(nodes), do: Enum.map(nodes, &resolve(&1, slots))
def resolve(text, _slots) when is_binary(text), do: text
def resolve(value, _slots) when is_atom(value) or is_number(value), do: to_string(value)
end
Loading