diff --git a/README.md b/README.md index bf50303..73ba112 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,15 @@ Polling web app, where admin selects one question, and the audience can see/answ ## Screens -![polls](html/screenshots/polls.jpg) -![question](html/screenshots/question.jpg) -![answers](html/screenshots/answers.jpg) -![edit](html/screenshots/edit_poll.jpg) +![polls](_html/screenshots/polls.jpg) -## Deploy procedure +![question](_html/screenshots/question.jpg) + +![answers](_html/screenshots/answers.jpg) + +![edit](_html/screenshots/edit_poll.jpg) + +## Deploy Refer to Gigalixir [documentation](https://gigalixir.readthedocs.io/en/latest/main.html#deploy) diff --git a/TODO.md b/TODO.md index 1e811ea..5b6d91b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,24 @@ # TODO +Step 1 - Manage + +* UI: Button "Pull" question - set `active` to `true`; and redirect_back + - show + - index +* UI: Button "Stop" finds active question and set `active` to FALSE + +Step 2 - Public + +* Navigate(of refresh) `poll_page(:id)` displays currently active question + 1) Poll does not have active question: display welcoming text + 2) Poll has active question: display question + +Step 3 - Public when question has been answered + +* to be continued... + +# Plane + - Build application with Phoenix framework * mix.new app * models @@ -42,13 +61,16 @@ * Submit answer * Show answers - admin - * CRUD poll. Need for grouping questions. Public-side does not know about quiz. + * CRUD poll. Need for grouping questions. Each poll consists of questions. + Only one question can be active(per poll). * CRUD question Each question consists of answers. Answers can be "radio buttons" or "check boxes". - Each question has one answer "Other" - input[type=text]. Optional. + Each question has special answer: "Other" input[type=text]. Optional. * Show question answers + - "Pull"-action button + - Next/Prev links ```example Question: diff --git a/html/admin_edit_question.html b/_html/admin_edit_question.html similarity index 100% rename from html/admin_edit_question.html rename to _html/admin_edit_question.html diff --git a/html/admin_index.html b/_html/admin_index.html similarity index 100% rename from html/admin_index.html rename to _html/admin_index.html diff --git a/html/admin_poll.html b/_html/admin_poll.html similarity index 100% rename from html/admin_poll.html rename to _html/admin_poll.html diff --git a/html/admin_show_question.html b/_html/admin_show_question.html similarity index 99% rename from html/admin_show_question.html rename to _html/admin_show_question.html index 163cddc..e2908c9 100644 --- a/html/admin_show_question.html +++ b/_html/admin_show_question.html @@ -83,7 +83,7 @@ ← Prev
- Next → + Next →
diff --git a/html/answers.html b/_html/answers.html similarity index 100% rename from html/answers.html rename to _html/answers.html diff --git a/html/index.html b/_html/index.html similarity index 100% rename from html/index.html rename to _html/index.html diff --git a/html/poll.html b/_html/poll.html similarity index 100% rename from html/poll.html rename to _html/poll.html diff --git a/html/screenshots/answers.jpg b/_html/screenshots/answers.jpg similarity index 100% rename from html/screenshots/answers.jpg rename to _html/screenshots/answers.jpg diff --git a/html/screenshots/edit_poll.jpg b/_html/screenshots/edit_poll.jpg similarity index 100% rename from html/screenshots/edit_poll.jpg rename to _html/screenshots/edit_poll.jpg diff --git a/html/screenshots/manage_poll.jpg b/_html/screenshots/manage_poll.jpg similarity index 100% rename from html/screenshots/manage_poll.jpg rename to _html/screenshots/manage_poll.jpg diff --git a/html/screenshots/polls.jpg b/_html/screenshots/polls.jpg similarity index 100% rename from html/screenshots/polls.jpg rename to _html/screenshots/polls.jpg diff --git a/html/screenshots/question.jpg b/_html/screenshots/question.jpg similarity index 100% rename from html/screenshots/question.jpg rename to _html/screenshots/question.jpg diff --git a/html/styles/main.css b/_html/styles/main.css similarity index 100% rename from html/styles/main.css rename to _html/styles/main.css diff --git a/assets/css/app.css b/assets/css/app.css index 0767dfa..6f93050 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,6 +1,14 @@ /* This file is for your main application css. */ /* theme_default.css */ + +.row-no-media { + flex-direction: row; +} +.row-no-media .column { + margin-bottom: inherit; +} + .title-adaptive { font-size: calc(24px + (36 - 24) * ((100vw - 320px) / 680)); } @@ -45,6 +53,13 @@ border-left: .3rem solid #f53131; } +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} @@ -277,66 +292,6 @@ } } -.prettyprint.code { - border: 0; - border-left: .3rem solid #9b4dca; - color: #655d5d -} - -.prettyprint.code .str { - color: #4b8b8b -} - -.prettyprint.code .kwd { - color: #8464c4 -} - -.prettyprint.code .com { - color: #adadad -} - -.prettyprint.code .typ { - color: #7272ca -} - -.prettyprint.code .lit { - color: #9b4dca -} - -.prettyprint.code .pun { - color: #5485b6 -} - -.prettyprint.code .clo, -.prettyprint.code .opn { - color: #f4ecec -} - -.prettyprint.code .atn, -.prettyprint.code .tag { - color: #9b4dca -} - -.prettyprint.code .atv { - color: #5485b6 -} - -.prettyprint.code .dec { - color: #b45a3c -} - -.prettyprint.code .var { - color: #ca4949 -} - -.prettyprint.code .fun { - color: #7272ca -} - -.prettyprint.code.lang-md * { - color: #655d5d -} - .carbonads { background-color: #f4f5f6; border-radius: 4px; @@ -388,30 +343,6 @@ margin-bottom: 0 } -.example { - margin-top: 4rem; - position: relative -} - -.example .example-header { - font-weight: 600; - margin-bottom: .5rem; - margin-top: 1.5rem -} - -.example h1, -.example h2, -.example h3, -.example h4, -.example h5, -.example h6 { - margin-bottom: 1rem -} - -.example form { - margin-bottom: 0 -} - .heading-font-size { color: #999; font-size: 1.2rem; @@ -460,13 +391,3 @@ text-align: center; text-transform: uppercase } - -.example .container { - padding-left: 0; - padding-right: 0 -} - -.example.row, -.example .row { - margin-bottom: 0 -} diff --git a/lib/instant_poll/ecto_type/strings_list.ex b/lib/instant_poll/ecto_type/strings_list.ex new file mode 100644 index 0000000..b5fe1ce --- /dev/null +++ b/lib/instant_poll/ecto_type/strings_list.ex @@ -0,0 +1,22 @@ +defmodule InstantPoll.EctoType.StringsList do + + @behaviour Ecto.Type + def type, do: {:array, :string} + + def cast(nil), do: {:ok, []} + def cast(arr) when is_list(arr) do + {:ok, reject_blank(arr)} + end + + def dump(val), do: {:ok, val} + def load(val), do: {:ok, val} + + + defp reject_blank(collection) do + Enum.reject(collection, fn(s) -> is_blank(s) end) + end + + defp is_blank(str) do + String.length(String.trim(str)) == 0 + end +end diff --git a/lib/instant_poll/polls/poll.ex b/lib/instant_poll/polls/poll.ex index dce8ae5..bf287fc 100644 --- a/lib/instant_poll/polls/poll.ex +++ b/lib/instant_poll/polls/poll.ex @@ -8,12 +8,12 @@ defmodule InstantPoll.Polls.Poll do schema "polls" do field :archived, :boolean, default: false field :name, :string + belongs_to :active_question, Question has_many :questions, Question timestamps() end - @doc false def changeset(%Poll{} = poll, attrs) do poll |> cast(attrs, [:name, :archived]) diff --git a/lib/instant_poll/polls/polls.ex b/lib/instant_poll/polls/polls.ex index 68d1a16..1856608 100644 --- a/lib/instant_poll/polls/polls.ex +++ b/lib/instant_poll/polls/polls.ex @@ -1,7 +1,4 @@ defmodule InstantPoll.Polls do - @moduledoc """ - The Polls context. - """ import Ecto.Query, warn: false alias InstantPoll.Repo @@ -9,98 +6,30 @@ defmodule InstantPoll.Polls do alias InstantPoll.Polls.Poll alias InstantPoll.Polls.Question - @doc """ - Returns the list of polls. - - ## Examples - - iex> list_polls() - [%Poll{}, ...] - - """ def list_polls do Poll |> order_by(desc: :id) |> Repo.all end - @doc """ - Gets a single poll. - - Raises `Ecto.NoResultsError` if the Poll does not exist. - - ## Examples - - iex> get_poll!(123) - %Poll{} - - iex> get_poll!(456) - ** (Ecto.NoResultsError) - - """ def get_poll!(id), do: Repo.get!(Poll, id) - @doc """ - Creates a poll. - - ## Examples - - iex> create_poll(%{field: value}) - {:ok, %Poll{}} - - iex> create_poll(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def create_poll(attrs \\ %{}) do %Poll{} |> Poll.changeset(attrs) |> Repo.insert() end - @doc """ - Updates a poll. - - ## Examples - - iex> update_poll(poll, %{field: new_value}) - {:ok, %Poll{}} - - iex> update_poll(poll, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ def update_poll(%Poll{} = poll, attrs) do poll |> Poll.changeset(attrs) |> Repo.update() end - @doc """ - Deletes a Poll. - - ## Examples - - iex> delete_poll(poll) - {:ok, %Poll{}} - - iex> delete_poll(poll) - {:error, %Ecto.Changeset{}} - - """ def delete_poll(%Poll{} = poll) do Repo.delete(poll) end - @doc """ - Returns an `%Ecto.Changeset{}` for tracking poll changes. - - ## Examples - - iex> change_poll(poll) - %Ecto.Changeset{source: %Poll{}} - - """ def change_poll(%Poll{} = poll) do Poll.changeset(poll, %{}) end diff --git a/lib/instant_poll/polls/question.ex b/lib/instant_poll/polls/question.ex index 99158c3..41b4431 100644 --- a/lib/instant_poll/polls/question.ex +++ b/lib/instant_poll/polls/question.ex @@ -3,27 +3,22 @@ defmodule InstantPoll.Polls.Question do import Ecto.Changeset alias InstantPoll.Polls.Question alias InstantPoll.Polls.Poll + alias InstantPoll.EctoType.StringsList schema "questions" do - field :answers, {:array, :string} - field :multiple, :boolean, default: false field :name, :string + field :answers, StringsList, default: [] + field :multiple, :boolean, default: false field :other_answer, :boolean, default: false belongs_to :poll, Poll timestamps() end - @doc false def changeset(%Question{} = question, attrs) do question - |> cast(attrs, [:name, :poll_id, :multiple, :answers, :other_answer]) - |> put_change( - :answers, - attrs["answers"] == nil || - Enum.reject(attrs["answers"], fn(s) -> String.length(String.trim s) == 0 end) - ) + |> cast(attrs, [:name, :answers, :poll_id, :multiple, :other_answer]) |> validate_required([:name, :poll_id, :multiple, :other_answer]) end end diff --git a/lib/instant_poll_web/controllers/cms/question_controller.ex b/lib/instant_poll_web/controllers/cms/question_controller.ex index b36057b..2a81bae 100644 --- a/lib/instant_poll_web/controllers/cms/question_controller.ex +++ b/lib/instant_poll_web/controllers/cms/question_controller.ex @@ -5,6 +5,13 @@ defmodule InstantPollWeb.CMS.QuestionController do alias InstantPoll.Polls.Poll alias InstantPoll.Polls.Question + def show(conn, %{"poll_id" => poll_id, "id" => id}) do + poll = Polls.get_poll!(poll_id) + question = Polls.get_question!(poll_id, id) + + render(conn, :show, question: question,poll: poll) + end + def new(conn, %{"poll_id" => poll_id}) do poll = Polls.get_poll!(poll_id) question = %Question{} @@ -43,7 +50,7 @@ defmodule InstantPollWeb.CMS.QuestionController do {:ok, question} -> conn |> put_flash(:info, "Question updated successfully.") - |> redirect(to: cms_poll_path(conn, :show, poll)) + |> redirect(to: cms_poll_question_path(conn, :show, poll, question)) # {:error, %Ecto.Changeset{} = changeset} -> # render(conn, "edit.html", poll: poll, changeset: changeset) end diff --git a/lib/instant_poll_web/templates/cms/poll/show.html.eex b/lib/instant_poll_web/templates/cms/poll/show.html.eex index 1ace9fd..93f83c8 100644 --- a/lib/instant_poll_web/templates/cms/poll/show.html.eex +++ b/lib/instant_poll_web/templates/cms/poll/show.html.eex @@ -34,7 +34,7 @@ <%= link question.name, to: cms_poll_question_path(@conn, :show, @poll, question) %>
<%= for answer <- question.answers do %> - 80% <%= answer %>
+ 0% <%= answer %>
<% end %>
diff --git a/lib/instant_poll_web/templates/cms/question/show.html.eex b/lib/instant_poll_web/templates/cms/question/show.html.eex new file mode 100644 index 0000000..95a1b9d --- /dev/null +++ b/lib/instant_poll_web/templates/cms/question/show.html.eex @@ -0,0 +1,44 @@ +<%= link "Polls", to: cms_poll_path(@conn, :index) %> > +<%= link @poll.name, to: cms_poll_path(@conn, :show, @poll) %> +

<%= @question.name %>

+ +
+
+ ← Prev +
+
+ <%= if @question.id == @poll.active_question_id do %> + Active + <% else %> + <%= link "Pull", + to: cms_poll_question_path(@conn, :update, @poll, @question), + method: :put, + class: "button" %> + <% end %> +
+
+ Next → +
+
+ +

Results:

+ +
+ 80% IntelliJ IDEA family (RubyMine, PhpStorm, WebStorm, etc.) +
+ + 5% Atom +
+ + 5% VS Code +
+ + 2% Emacs or Vim +
+ + 2% Emacs or Vim +
+ + 5% Other: Gedit; Cloud9; Nano +
+
diff --git a/lib/instant_poll_web/templates/layout/app.html.eex b/lib/instant_poll_web/templates/layout/app.html.eex index acd9a7c..608d437 100644 --- a/lib/instant_poll_web/templates/layout/app.html.eex +++ b/lib/instant_poll_web/templates/layout/app.html.eex @@ -5,10 +5,6 @@ - - - - @@ -16,39 +12,11 @@ - - - - - - - - - - - - - - - - - - - - Instan Poll - - - - - - - - "> @@ -93,7 +61,7 @@

Build with Milligram CSS framework. Hosted on Gigalixir. - Licensed under the MIT License.

+ Licensed under the MIT License.

diff --git a/priv/repo/migrations/20181205145506_create_questions.exs b/priv/repo/migrations/20181205145506_create_questions.exs index 1dcc103..59af0f7 100644 --- a/priv/repo/migrations/20181205145506_create_questions.exs +++ b/priv/repo/migrations/20181205145506_create_questions.exs @@ -7,11 +7,15 @@ defmodule InstantPoll.Repo.Migrations.CreateQuestions do add :multiple, :boolean, default: false, null: false add :answers, {:array, :string} add :other_answer, :boolean, default: false, null: false - add :poll_id, references(:polls, on_delete: :nothing) + add :poll_id, references(:polls, on_delete: :delete_all), null: false timestamps() end create index(:questions, [:poll_id]) + + alter table(:polls) do + add :active_question_id, references(:questions, on_delete: :nilify_all) + end end end