Skip to content

Commit

Permalink
[CONFIGURATOR] Versioning + UI (#1013)
Browse files Browse the repository at this point in the history
* Add versioning feature (1st iteration)

- Reorganize schemas and tables constraints (version_id)
- Add Version associations (and assoc changesets)
- Allow new config version creation in UI (update forms)

* Add 'back to home' redirect button

* Fix all character views to work with new versions' behavior

* Fix all game_configuration views to work with new versions' behavior

* Fix error cases in character controller

* Fix all map_configuration views to work with new versions' behavior

* Fix all skills views to work with new versions' behavior

* Fix all consumable_items views to work with new versions' behavior

* Fix seeds for both autobattler and arena apps, improve UI styles and fix form bugs

* Add gh issue link to TODOs

* Refactor function to list curse skills grouped by type

* Current version now takes all consumable items, also handle items without any effect

* Filter active items when encoding the configuration

* Rename :version_id path param due to override and fix warnings in code

* Format code

* Add missing param in map_confguration_form

* Add nil case for skill_select and update test cases for CharacterController

* Update test cases for MapConfigurationController

* Handle nil case in version creation

* Create version for Autobattler tests
  • Loading branch information
Nico-Sanchez authored Jan 6, 2025
1 parent 57641d9 commit e511e14
Show file tree
Hide file tree
Showing 67 changed files with 971 additions and 373 deletions.
3 changes: 2 additions & 1 deletion apps/champions/lib/champions/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ defmodule Champions.Config do
@doc """
Imports the skills configuration from 'skills.json' in the app's priv folder.
"""
def import_skill_config() do
def import_skill_config(game_id) do
{:ok, skills_json} =
Application.app_dir(:champions, "priv/skills.json")
|> File.read()

Jason.decode!(skills_json, [{:keys, :atoms}])
|> Enum.map(fn skill -> Map.put(skill, :game_id, game_id) end)
|> Skills.upsert_skills()
end

Expand Down
24 changes: 23 additions & 1 deletion apps/champions/test/support/test_utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@ defmodule Champions.TestUtils do
Utility functions for tests.
"""

@doc """
Generate a version.
"""
def version_fixture(attrs \\ %{}) do
{:ok, version} =
attrs
|> Enum.into(%{
name: "some name"
})
|> GameBackend.Configuration.create_version()

version
end

def build_character(params \\ %{}) do
version = version_fixture()

Map.merge(
%{
game_id: 2,
Expand All @@ -15,7 +31,8 @@ defmodule Champions.TestUtils do
base_health: 100,
base_defense: 0,
basic_skill: build_skill(%{name: "Default Basic Skill"}),
ultimate_skill: build_skill(%{name: "Default Ultimate Skill"})
ultimate_skill: build_skill(%{name: "Default Ultimate Skill"}),
version_id: version.id
},
params
)
Expand All @@ -35,9 +52,14 @@ defmodule Champions.TestUtils do
end

def build_skill(params \\ %{}) do
version = version_fixture()
game_id = GameBackend.Utils.get_game_id(:champions_of_mirra)

Map.merge(
%{
name: "Default Name",
version_id: version.id,
game_id: game_id,
energy_regen: 0,
animation_duration: 0,
mechanics: [
Expand Down
37 changes: 37 additions & 0 deletions apps/configurator/assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,41 @@
@import "tailwindcss/components";
@import "tailwindcss/utilities";


.form-container {
display: flex; /* Make the container a flex container */
flex-wrap: wrap; /* Allow items to wrap to the next line if needed */
align-items: center;
justify-content: center;
}

.form-container > div {
padding: 10px;
}

.input-item {
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
align-items: center;
flex-wrap: wrap;
}

.input-item > div {
padding: 10px;
flex: 0 0 150px;
}

.left{
position: absolute;
top: 0px;
left: 10px;
text-align: left;
}

.right{
position: absolute;
top: 0px;
right: 10px;
}

/* This file is for your main application CSS */
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,42 @@ defmodule ConfiguratorWeb.CoreComponents do
"""
end

@doc """
Renders a css flex container styled simple form.
## Examples
<.flex_form for={@form} phx-change="validate" phx-submit="save">
<.input field={@form[:email]} label="Email"/>
<.input field={@form[:username]} label="Username" />
<:actions>
<.button>Save</.button>
</:actions>
</.flex_form>
"""
attr :for, :any, required: true, doc: "the datastructure for the form"
attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"

attr :rest, :global,
include: ~w(autocomplete name rel action enctype method novalidate target multipart),
doc: "the arbitrary HTML attributes to apply to the form tag"

slot :inner_block, required: true
slot :actions, doc: "the slot for form actions, such as a submit button"

def flex_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="form-container mt-10 space-y-8 bg-white">
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>
</div>
</div>
</.form>
"""
end

@doc """
Renders a button.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ defmodule ConfiguratorWeb.CustomComponents do
attr :form, :map, required: true
attr :field, :atom, required: true

def effect_form(%{form: %{params: %{"effect" => nil}}} = assigns) do
~H"""
<h3>No Effect</h3>
"""
end

def effect_form(assigns) do
~H"""
<.button type="button" phx-click={show_modal("effect-form")}>Edit effect</.button>
Expand All @@ -74,7 +80,11 @@ defmodule ConfiguratorWeb.CustomComponents do
/>
<.input field={mechanics_form[:modifier]} type="number" label="Modifier" step="any" />
<.input field={mechanics_form[:force]} type="number" label="Force" step="any" />
<.input field={effect_f[:execute_multiple_times]} type="checkbox" label="Execute mechanic multiple times" />
<.input
field={mechanics_form[:execute_multiple_times]}
type="checkbox"
label="Execute mechanic multiple times"
/>
<.input field={mechanics_form[:damage]} type="number" label="Damage amount" />
<.input field={mechanics_form[:effect_delay_ms]} type="number" label="Mechanic delay" />
<.input field={mechanics_form[:additive_duration_add_ms]} type="number" label="Additive duration to add ms" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<header class="px-4 sm:px-6 lg:px-8"></header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<main class="px-4 py-8 sm:px-6 lg:px-8">
<div class="mx-auto max-w-fit">
<.flash_group flash={@flash} />
<%= @inner_content %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
<!DOCTYPE html>
<style>
nav {
padding: 15px;
}
nav ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
}
nav a {
display: block;
}
.nav-left {
float: left;
}
.nav-left ul li {
float: left;
}
.nav-right ul li {
float: right;
}
</style>

<html lang="en" class="[scrollbar-gutter:stable]">
<head>
<meta charset="utf-8" />
Expand All @@ -12,22 +40,39 @@
</script>
</head>
<body class="bg-white antialiased">
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
<%= if @current_user do %>
<li class="text-[0.8125rem] leading-6 text-zinc-900">
<%= @current_user.email %>
</li>
<li>
<.link
href={~p"/users/log_out"}
method="delete"
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Log out
</.link>
</li>
<% end %>
</ul>
<nav class="nav">
<div class="nav-left">
<ul>
<li>
<a>
<.link href={~p"/"} class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700">
<.icon name="hero-arrow-left-solid" class="h-3 w-3" /> Back to Home
</.link>
</a>
</li>
</ul>
</div>
<div class="nav-right">
<ul>
<%= if @current_user do %>
<li>
<a>
<.link
href={~p"/users/log_out"}
method="delete"
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Log out
</.link>
</a>
</li>
<li class="text-[0.8125rem] leading-6 text-zinc-900 px-4">
<a><%= @current_user.email %></a>
</li>
<% end %>
</ul>
</div>
</nav>
<%= @inner_content %>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ defmodule ConfiguratorWeb.CharacterController do
alias GameBackend.Units.Characters
alias GameBackend.Units.Characters.Character
alias GameBackend.Configuration
alias GameBackend.Utils

def index(conn, _params) do
characters = Characters.get_curse_characters()
render(conn, :index, characters: characters)
def index(conn, %{"version_id" => version_id}) do
characters = Characters.get_curse_characters_by_version(version_id)
render(conn, :index, characters: characters, version_id: version_id)
end

def new(conn, _params) do
def new(conn, %{"version_id" => version_id}) do
changeset = Ecto.Changeset.change(%Character{})
skills = get_curse_skills_by_type()
render(conn, :new, changeset: changeset, skills: skills)
version = Configuration.get_version!(version_id)
skills = Utils.list_curse_skills_by_version_grouped_by_type(version.id)
render(conn, :new, changeset: changeset, skills: skills, version: version)
end

def create(conn, %{"character" => character_params}) do
Expand All @@ -26,11 +28,12 @@ defmodule ConfiguratorWeb.CharacterController do
{:ok, character} ->
conn
|> put_flash(:success, "Character created successfully.")
|> redirect(to: ~p"/characters/#{character}")
|> redirect(to: ~p"/versions/#{character.version_id}/characters/#{character}")

{:error, %Ecto.Changeset{} = changeset} ->
skills = get_curse_skills_by_type()
render(conn, :new, changeset: changeset, skills: skills)
version = Configuration.get_version!(character_params["version_id"])
skills = Utils.list_curse_skills_by_version_grouped_by_type(version.id)
render(conn, :new, changeset: changeset, skills: skills, version: version)
end
end

Expand All @@ -43,8 +46,9 @@ defmodule ConfiguratorWeb.CharacterController do
def edit(conn, %{"id" => id}) do
character = Characters.get_character(id)
changeset = Ecto.Changeset.change(character)
skills = get_curse_skills_by_type()
render(conn, :edit, character: character, changeset: changeset, skills: skills)
version = Configuration.get_version!(character.version_id)
skills = Utils.list_curse_skills_by_version_grouped_by_type(version.id)
render(conn, :edit, character: character, changeset: changeset, skills: skills, version: version)
end

def update(conn, %{"id" => id, "character" => character_params}) do
Expand All @@ -54,25 +58,22 @@ defmodule ConfiguratorWeb.CharacterController do
{:ok, character} ->
conn
|> put_flash(:success, "Character updated successfully.")
|> redirect(to: ~p"/characters/#{character}")
|> redirect(to: ~p"/versions/#{character.version_id}/characters/#{character}")

{:error, %Ecto.Changeset{} = changeset} ->
skills = get_curse_skills_by_type()
render(conn, :edit, character: character, changeset: changeset, skills: skills)
version = Configuration.get_version!(character.version_id)
skills = Utils.list_curse_skills_by_version_grouped_by_type(version.id)
render(conn, :edit, character: character, changeset: changeset, skills: skills, version: version)
end
end

def delete(conn, %{"id" => id}) do
character = Characters.get_character(id)
version_id = character.version_id
{:ok, _character} = Characters.delete_character(character)

conn
|> put_flash(:success, "Character deleted successfully.")
|> redirect(to: ~p"/characters")
end

defp get_curse_skills_by_type() do
GameBackend.Units.Skills.list_curse_skills()
|> Enum.group_by(& &1.type)
|> redirect(to: ~p"/versions/#{version_id}/characters")
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ defmodule ConfiguratorWeb.CharacterHTML do
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true
attr :skills, :list, required: true
attr :version, GameBackend.Configuration.Version, required: true

def character_form(assigns)

attr :field, Phoenix.HTML.FormField, required: true
attr :label, :string, required: true
attr :skills, :list, required: true

def skill_select(%{skills: nil} = assigns) do
~H"""
<h3>No skills</h3>
"""
end

def skill_select(assigns) do
~H"""
<.input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@
<.error :if={@changeset.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input
field={f[:version_id]}
type="select"
options={Enum.map(GameBackend.Configuration.list_versions(), fn version -> {version.name, version.id} end)}
label="Version"
/>
<p class="font-semibold"><%= "Version: #{@version.name}" %></p>
<.input type="hidden" field={f[:version_id]} value={@version.id} />
<.input field={f[:name]} type="text" label="Name" />
<.input field={f[:active]} type="checkbox" label="Active" />
<.input field={f[:base_speed]} type="number" label="Base speed" step="any" />
Expand Down
Loading

0 comments on commit e511e14

Please sign in to comment.