Skip to content

Commit 3bfe7fd

Browse files
authored
feat: add movies pagination
1 parent 6f212b2 commit 3bfe7fd

File tree

10 files changed

+319
-3
lines changed

10 files changed

+319
-3
lines changed

lib/media_server/paginater.ex

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
defimpl Scrivener.Paginater, for: List do
2+
# An implementation of the `Scrivener.Paginater` protocol to extend `Scrivener.Paginater.paginate/2`
3+
alias Scrivener.Config
4+
alias Scrivener.Page
5+
6+
@doc ~s"""
7+
Implementation of `Scrivener.Paginater` protocol to extend the `Scrivener.Paginater.paginate/2` function.
8+
"""
9+
@spec paginate(any, map | Keyword.t()) :: Scrivener.Page.t()
10+
def paginate(entries, %Config{page_number: page_number, page_size: page_size}) do
11+
total_entries = length(entries)
12+
13+
%Page{
14+
page_size: page_size,
15+
page_number: page_number,
16+
entries: entries(entries, page_number, page_size),
17+
total_entries: total_entries,
18+
total_pages: total_pages(total_entries, page_size)
19+
}
20+
end
21+
22+
defp entries(entries, page_number, page_size) do
23+
offset = page_size * (page_number - 1)
24+
Enum.slice(entries, offset, page_size)
25+
end
26+
27+
defp total_pages(total_entries, page_size) do
28+
ceiling(total_entries / page_size)
29+
end
30+
31+
defp ceiling(float) do
32+
t = trunc(float)
33+
34+
case float - t do
35+
neg when neg < 0 ->
36+
t
37+
38+
pos when pos > 0 ->
39+
t + 1
40+
41+
_ ->
42+
t
43+
end
44+
end
45+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
defmodule MediaServerWeb.Components.PaginationComponent do
2+
use Phoenix.Component
3+
4+
def render(assigns) do
5+
~H"""
6+
<nav class="bg-gray-50 mt-12 py-3 flex items-center justify-between border-t border-gray-200 bottom-0 sticky pb-4">
7+
8+
<div class="hidden sm:block">
9+
<p class="text-sm text-gray-700">
10+
Showing
11+
<span class="font-medium"><%= assigns.page_number %></span>
12+
of
13+
<span class="font-medium"><%= assigns.total_pages %></span>
14+
pages
15+
</p>
16+
</div>
17+
18+
<div class="flex-1 flex justify-between sm:justify-end">
19+
20+
<div>
21+
<%= if assigns.page_number > 1 do %>
22+
<%= live_redirect to: assigns.previous_link, class: "relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
23+
Previous
24+
<% end %>
25+
<% end %>
26+
</div>
27+
28+
<%= if assigns.page_number !== assigns.total_pages do %>
29+
<%= live_redirect to: assigns.next_link, class: "ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" do %>
30+
Next
31+
<% end %>
32+
<% end %>
33+
</div>
34+
</nav>
35+
"""
36+
end
37+
end

lib/media_server_web/helpers.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,12 @@ defmodule MediaServerWeb.Helpers do
66
def percentage_complete_from_seconds(current_time, duration) do
77
current_time / duration * 100
88
end
9+
10+
def get_pagination_previous_link(page_number) do
11+
page_number - 1
12+
end
13+
14+
def get_pagination_next_link(page_number, total_pages) do
15+
page_number + 1
16+
end
917
end

lib/media_server_web/live/movies_live/index.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ defmodule MediaServerWeb.MoviesLive.Index do
44
alias MediaServerWeb.Repositories.Movies
55

66
@impl true
7+
def handle_params(%{"page" => page}, _url, socket) do
8+
{
9+
:noreply,
10+
socket
11+
|> assign(:page_title, "Movies")
12+
|> assign(:movies, Scrivener.paginate(Movies.get_all(), %{"page" => page, "page_size" => "50"}))
13+
}
14+
end
15+
716
def handle_params(_params, _url, socket) do
817
{
918
:noreply,
1019
socket
1120
|> assign(:page_title, "Movies")
12-
|> assign(:movies, Movies.get_all())
21+
|> assign(:movies, Scrivener.paginate(Movies.get_all(), %{"page" => "1", "page_size" => "50"}))
1322
}
1423
end
1524
end

lib/media_server_web/live/movies_live/index.html.heex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,12 @@
2222
}) %>
2323

2424
<% end %>
25-
</div>
25+
</div>
26+
27+
<%= MediaServerWeb.Components.PaginationComponent.render(%{
28+
page_number: @movies.page_number,
29+
total_pages: @movies.total_pages,
30+
previous_link: Routes.movies_index_path(@socket, :index, page: MediaServerWeb.Helpers.get_pagination_previous_link(@movies.page_number)),
31+
next_link: Routes.movies_index_path(@socket, :index, page: MediaServerWeb.Helpers.get_pagination_next_link(@movies.page_number, @movies.total_pages))
32+
}) %>
2633
</div>

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ defmodule MediaServer.MixProject do
5555
{:httpoison, "~> 1.8"},
5656
{:excoveralls, "~> 0.10", only: :test},
5757
{:tailwind, "~> 0.1", runtime: Mix.env() == :dev},
58-
{:cors_plug, "~> 3.0"}
58+
{:cors_plug, "~> 3.0"},
59+
{:scrivener, "~> 2.0"}
5960
]
6061
end
6162

mix.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"rambo": {:hex, :rambo, "0.3.4", "8962ac3bd1a633ee9d0e8b44373c7913e3ce3d875b4151dcd060886092d2dce7", [:mix], [], "hexpm", "0cc54ed089fbbc84b65f4b8a774224ebfe60e5c80186fafc7910b3e379ad58f1"},
4444
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
4545
"reverse_proxy_plug": {:hex, :reverse_proxy_plug, "2.1.0", "29be90880c1f5ccbcc10ff40ba553ba150b4341a3d76b5945f42433283fc445c", [:mix], [{:cowboy, "~> 2.4", [hex: :cowboy, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.2", [hex: :httpoison, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: false]}, {:tesla, "~> 1.4", [hex: :tesla, repo: "hexpm", optional: true]}], "hexpm", "698305ad5ecc410cd81be5c2e4580c623321264abf6f1e8e23a41ae5d78ca108"},
46+
"scrivener": {:hex, :scrivener, "2.7.2", "1d913c965ec352650a7f864ad7fd8d80462f76a32f33d57d1e48bc5e9d40aba2", [:mix], [], "hexpm", "7866a0ec4d40274efbee1db8bead13a995ea4926ecd8203345af8f90d2b620d9"},
4647
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
4748
"swoosh": {:hex, :swoosh, "1.6.4", "ce3a4bf3e5276fd114178ebc5ed072ee0c177a7b3a09e5992aa005778ac143c2", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad4c8b534812433730b6241a1d9df38b1da75fdfa340f51887a31d7e9343fffe"},
4849
"tailwind": {:hex, :tailwind, "0.1.5", "5561bed6c114434415077972f6d291e7d43b258ef0ee756bda1ead7293811f61", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "3be21a0ddec7fc29b323ee72bed7516078a2787f7b142e455698a2209296e2a5"},

priv/static/assets/app.css

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,11 @@ select {
658658
position: relative;
659659
}
660660

661+
.sticky {
662+
position: -webkit-sticky;
663+
position: sticky;
664+
}
665+
661666
.inset-0 {
662667
top: 0px;
663668
right: 0px;
@@ -682,6 +687,10 @@ select {
682687
right: 0px;
683688
}
684689

690+
.bottom-0 {
691+
bottom: 0px;
692+
}
693+
685694
.z-10 {
686695
z-index: 10;
687696
}
@@ -744,6 +753,18 @@ select {
744753
margin-top: 0.75rem;
745754
}
746755

756+
.mt-12 {
757+
margin-top: 3rem;
758+
}
759+
760+
.-mt-px {
761+
margin-top: -1px;
762+
}
763+
764+
.mr-3 {
765+
margin-right: 0.75rem;
766+
}
767+
747768
.mb-4 {
748769
margin-bottom: 1rem;
749770
}
@@ -868,6 +889,10 @@ select {
868889
width: 9rem;
869890
}
870891

892+
.w-0 {
893+
width: 0px;
894+
}
895+
871896
.min-w-0 {
872897
min-width: 0px;
873898
}
@@ -1081,6 +1106,10 @@ select {
10811106
border-bottom-width: 1px;
10821107
}
10831108

1109+
.border-t-2 {
1110+
border-top-width: 2px;
1111+
}
1112+
10841113
.border-slate-200 {
10851114
--tw-border-opacity: 1;
10861115
border-color: rgb(226 232 240 / var(--tw-border-opacity));
@@ -1095,6 +1124,16 @@ select {
10951124
border-color: transparent;
10961125
}
10971126

1127+
.border-gray-200 {
1128+
--tw-border-opacity: 1;
1129+
border-color: rgb(229 231 235 / var(--tw-border-opacity));
1130+
}
1131+
1132+
.border-slate-500 {
1133+
--tw-border-opacity: 1;
1134+
border-color: rgb(100 116 139 / var(--tw-border-opacity));
1135+
}
1136+
10981137
.bg-white {
10991138
--tw-bg-opacity: 1;
11001139
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@@ -1224,6 +1263,11 @@ select {
12241263
padding-bottom: 3rem;
12251264
}
12261265

1266+
.py-3 {
1267+
padding-top: 0.75rem;
1268+
padding-bottom: 0.75rem;
1269+
}
1270+
12271271
.pr-2 {
12281272
padding-right: 0.5rem;
12291273
}
@@ -1252,6 +1296,18 @@ select {
12521296
padding-bottom: 4rem;
12531297
}
12541298

1299+
.pb-4 {
1300+
padding-bottom: 1rem;
1301+
}
1302+
1303+
.pr-1 {
1304+
padding-right: 0.25rem;
1305+
}
1306+
1307+
.pl-1 {
1308+
padding-left: 0.25rem;
1309+
}
1310+
12551311
.pl-3 {
12561312
padding-left: 0.75rem;
12571313
}
@@ -1427,6 +1483,11 @@ select {
14271483
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
14281484
}
14291485

1486+
.hover\:border-gray-300:hover {
1487+
--tw-border-opacity: 1;
1488+
border-color: rgb(209 213 219 / var(--tw-border-opacity));
1489+
}
1490+
14301491
.hover\:bg-gray-700:hover {
14311492
--tw-bg-opacity: 1;
14321493
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
@@ -1467,6 +1528,11 @@ select {
14671528
color: rgb(243 244 246 / var(--tw-text-opacity));
14681529
}
14691530

1531+
.hover\:text-gray-700:hover {
1532+
--tw-text-opacity: 1;
1533+
color: rgb(55 65 81 / var(--tw-text-opacity));
1534+
}
1535+
14701536
.hover\:underline:hover {
14711537
-webkit-text-decoration-line: underline;
14721538
text-decoration-line: underline;
@@ -1568,6 +1634,10 @@ select {
15681634
grid-template-columns: repeat(6, minmax(0, 1fr));
15691635
}
15701636

1637+
.sm\:justify-end {
1638+
justify-content: flex-end;
1639+
}
1640+
15711641
.sm\:gap-6 {
15721642
gap: 1.5rem;
15731643
}
@@ -1582,6 +1652,11 @@ select {
15821652
padding-right: 1.5rem;
15831653
}
15841654

1655+
.sm\:px-0 {
1656+
padding-left: 0px;
1657+
padding-right: 0px;
1658+
}
1659+
15851660
.sm\:pb-32 {
15861661
padding-bottom: 8rem;
15871662
}
@@ -1593,6 +1668,14 @@ select {
15931668
}
15941669

15951670
@media (min-width: 768px) {
1671+
.md\:-mt-px {
1672+
margin-top: -1px;
1673+
}
1674+
1675+
.md\:flex {
1676+
display: flex;
1677+
}
1678+
15961679
.md\:hidden {
15971680
display: none;
15981681
}

0 commit comments

Comments
 (0)