diff --git a/lib/fun_with_flags/ui/templates.ex b/lib/fun_with_flags/ui/templates.ex index fa039d1..523e2df 100644 --- a/lib/fun_with_flags/ui/templates.ex +++ b/lib/fun_with_flags/ui/templates.ex @@ -97,6 +97,10 @@ defmodule FunWithFlags.UI.Templates do Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S") end + def format_utc_timestamp(%NaiveDateTime{} = ndt) do + Calendar.strftime(ndt, "%Y-%m-%d %H:%M:%S") + end + def format_utc_timestamp(nil), do: "" def audit_log_page_path(conn, page, assigns) do diff --git a/lib/fun_with_flags/ui/templates/index.html.eex b/lib/fun_with_flags/ui/templates/index.html.eex index 03b50c0..04dce3e 100644 --- a/lib/fun_with_flags/ui/templates/index.html.eex +++ b/lib/fun_with_flags/ui/templates/index.html.eex @@ -26,17 +26,26 @@
- +
- - + + + - + <%= for flag <- @flags do %> - + "> + + <% end %> @@ -57,9 +70,12 @@ - + diff --git a/lib/fun_with_flags/ui/utils.ex b/lib/fun_with_flags/ui/utils.ex index 701740e..48642f0 100644 --- a/lib/fun_with_flags/ui/utils.ex +++ b/lib/fun_with_flags/ui/utils.ex @@ -34,29 +34,9 @@ defmodule FunWithFlags.UI.Utils do def sort_flags(flags) do - Enum.sort(flags, &sorter/2) + Enum.sort_by(flags, & &1.name) end - defp sorter(a, b) do - sa = get_flag_status(a) - sb = get_flag_status(b) - - if sa == sb do - a.name < b.name - else - case sa do - :fully_open -> true - :half_open -> - case sb do - :fully_open -> false - :closed -> true - end - :closed -> false - end - end - end - - # Create new flags as disabled. # # Here we are converting a user-provided string to an atom, which is @@ -233,4 +213,5 @@ defmodule FunWithFlags.UI.Utils do |> List.last() |> String.length() end + end diff --git a/mix.lock b/mix.lock index 62073fc..59d1ff6 100644 --- a/mix.lock +++ b/mix.lock @@ -8,7 +8,7 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "fun_with_flags": {:git, "https://github.com/invideoio/fun_with_flags.git", "dd3bbe1b483afa2de2f9484f393d2f1ed9790e4a", [branch: "master"]}, + "fun_with_flags": {:git, "https://github.com/invideoio/fun_with_flags.git", "1c924332c1b0ec13e8440730767c75e1bb23f300", [branch: "master"]}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, diff --git a/priv/static/style.css b/priv/static/style.css index 62b7b7c..3481e11 100644 --- a/priv/static/style.css +++ b/priv/static/style.css @@ -17,7 +17,7 @@ colors } #fwf-top-bar { - background-color: #714a94; + background-color: #1e293b; } .navbar-collapse { @@ -91,234 +91,383 @@ button { } .pagination .page-item.active .page-link { - background-color: #714a94; - border-color: #714a94; + background-color: #2563eb; + border-color: #2563eb; +} + +/* ======================================== + Theme Transition + ======================================== */ + +body, +.card, .card-header, .card-body, .card-block, +.table, .table td, .table th, +.list-group-item, +.form-control, .input-group-addon, +.btn-secondary, .btn-primary, .btn-danger, +.alert, .page-link, +a, code, h1, h2, h3, h4, h5, h6 { + transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; } /* ======================================== Dark Theme ======================================== */ +/* --- Base --- */ [data-theme="dark"] body { - background-color: #1a1a2e; - color: #e0e0e0; + background-color: #0f0f0f; + color: #d4d4d8; } -/* Cards */ +/* --- Cards --- */ [data-theme="dark"] .card { - background-color: #16213e; - border-color: #2a2a4a; + background-color: #1c1c1e; + border-color: #2c2c2e; } [data-theme="dark"] .card-header { - background-color: #1a1a3e; - border-bottom-color: #2a2a4a; - color: #e0e0e0; + background-color: #232326; + border-bottom-color: #2c2c2e; + color: #e4e4e7; } -[data-theme="dark"] .card-body { - color: #e0e0e0; +[data-theme="dark"] .card-body, +[data-theme="dark"] .card-block { + color: #d4d4d8; } [data-theme="dark"] .card-header.bg-info { - background-color: #4e2a8e !important; + background-color: #1e3a5f !important; + border-bottom-color: #1e3a5f !important; } -/* Tables */ +/* --- Tables --- */ [data-theme="dark"] .table { - color: #e0e0e0; + color: #d4d4d8; } [data-theme="dark"] .table td, [data-theme="dark"] .table th { - border-top-color: #2a2a4a; + border-top-color: #2c2c2e; } [data-theme="dark"] .table-hover tbody tr:hover { - background-color: rgba(113, 74, 148, 0.15); + background-color: rgba(255, 255, 255, 0.05); } [data-theme="dark"] .thead-default th { - background-color: #1a1a3e; - color: #c0c0c0; - border-bottom-color: #2a2a4a; + background-color: #1c1c1e; + color: #a1a1aa; + border-bottom-color: #2c2c2e; } -/* List groups */ +/* --- List Groups --- */ [data-theme="dark"] .list-group-item { - background-color: #16213e; - border-color: #2a2a4a; - color: #e0e0e0; + background-color: #1c1c1e; + border-color: #2c2c2e; + color: #d4d4d8; } -/* Forms */ +/* --- Forms --- */ [data-theme="dark"] .form-control { - background-color: #0f3460; - border-color: #2a2a4a; - color: #e0e0e0; + background-color: #27272a; + border-color: #3f3f46; + color: #e4e4e7; } [data-theme="dark"] .form-control::placeholder { - color: #8888aa; + color: #71717a; } [data-theme="dark"] .form-control:focus { - background-color: #0f3460; - border-color: #714a94; - color: #e0e0e0; + background-color: #27272a; + border-color: #60a5fa; + color: #e4e4e7; + box-shadow: 0 0 0 0.2rem rgba(96, 165, 250, 0.25); } [data-theme="dark"] .form-control-label, [data-theme="dark"] label { - color: #e0e0e0; + color: #d4d4d8; } [data-theme="dark"] .form-text.text-muted, [data-theme="dark"] .text-muted { - color: #8888aa !important; + color: #71717a !important; } [data-theme="dark"] .form-check-label { - color: #e0e0e0; + color: #d4d4d8; +} + +[data-theme="dark"] .form-control-danger { + border-color: #ef4444; +} + +[data-theme="dark"] .form-control-feedback { + color: #f87171; +} + +[data-theme="dark"] .has-danger .form-control-label { + color: #f87171; +} + +[data-theme="dark"] select.form-control { + background-color: #27272a; + color: #e4e4e7; +} + +[data-theme="dark"] input[type="file"].form-control { + background-color: #27272a; + border-color: #3f3f46; + color: #d4d4d8; +} + +/* --- Input Group Addons --- */ +[data-theme="dark"] .input-group-addon { + background-color: #232326; + border-color: #3f3f46; + color: #a1a1aa; +} + +[data-theme="dark"] .input-group-btn .btn { + border-color: #3f3f46; +} + +/* --- Buttons --- */ +[data-theme="dark"] .btn-primary { + background-color: #2563eb; + border-color: #2563eb; + color: #fff; +} + +[data-theme="dark"] .btn-primary:hover, +[data-theme="dark"] .btn-primary:focus { + background-color: #1d4ed8; + border-color: #1d4ed8; + color: #fff; } -/* Buttons */ [data-theme="dark"] .btn-secondary { - background-color: #2a2a4a; - border-color: #3a3a5a; - color: #e0e0e0; + background-color: #27272a; + border-color: #3f3f46; + color: #d4d4d8; +} + +[data-theme="dark"] .btn-secondary:hover, +[data-theme="dark"] .btn-secondary:focus { + background-color: #3f3f46; + border-color: #52525b; + color: #fff; +} + +[data-theme="dark"] .btn-danger { + background-color: #dc2626; + border-color: #dc2626; + color: #fff; +} + +[data-theme="dark"] .btn-danger:hover, +[data-theme="dark"] .btn-danger:focus { + background-color: #b91c1c; + border-color: #b91c1c; + color: #fff; +} + +[data-theme="dark"] .btn-outline-danger { + color: #f87171; + border-color: #f87171; + background-color: transparent; +} + +[data-theme="dark"] .btn-outline-danger:hover { + background-color: #dc2626; + border-color: #dc2626; + color: #fff; +} + +[data-theme="dark"] .btn-outline-success { + color: #4ade80; + border-color: #4ade80; + background-color: transparent; } -[data-theme="dark"] .btn-secondary:hover { - background-color: #3a3a5a; - border-color: #4a4a6a; +[data-theme="dark"] .btn-outline-success:hover { + background-color: #16a34a; + border-color: #16a34a; color: #fff; } [data-theme="dark"] .btn-link { - color: #6cb4ee; + color: #60a5fa; } -/* Alerts */ +[data-theme="dark"] .btn-link:hover { + color: #93c5fd; +} + +/* --- Alerts --- */ [data-theme="dark"] .alert-danger { - background-color: #3d1111; - border-color: #5c1a1a; - color: #f5a5a5; + background-color: #1c1111; + border-color: #7f1d1d; + color: #fca5a5; } [data-theme="dark"] .alert-success { - background-color: #0d3320; - border-color: #1a5c38; - color: #a5f5c0; + background-color: #0a1f13; + border-color: #14532d; + color: #86efac; } [data-theme="dark"] .alert-warning { - background-color: #332b00; - border-color: #5c4d00; - color: #f5e5a5; + background-color: #1a1506; + border-color: #713f12; + color: #fde68a; } [data-theme="dark"] .alert-info { - background-color: #0a2540; - border-color: #14406a; - color: #a5d5f5; + background-color: #0c1929; + border-color: #1e3a5f; + color: #93c5fd; } -/* Badges */ +/* --- Badges --- */ [data-theme="dark"] .badge-success { - background-color: #1a8a45; + background-color: #15803d; + color: #fff; } [data-theme="dark"] .badge-danger { - background-color: #a53030; + background-color: #b91c1c; + color: #fff; } [data-theme="dark"] .badge-default { - background-color: #3a3a5a; - color: #c0c0c0; + background-color: #3f3f46; + color: #d4d4d8; } -/* Links */ +/* --- Links --- */ [data-theme="dark"] a { - color: #6cb4ee; + color: #60a5fa; } [data-theme="dark"] a:hover { - color: #9ccdf5; + color: #93c5fd; } -/* Input group addons (labels like "group name", "%") */ -[data-theme="dark"] .input-group-addon { - background-color: #1a1a3e; - border-color: #2a2a4a; - color: #c0c0c0; +/* --- Typography --- */ +[data-theme="dark"] h1, [data-theme="dark"] h2, +[data-theme="dark"] h3, [data-theme="dark"] h4, +[data-theme="dark"] h5, [data-theme="dark"] h6 { + color: #e4e4e7; +} + +[data-theme="dark"] strong { + color: #e4e4e7; +} + +[data-theme="dark"] code { + color: #fb923c; + background-color: #27272a; +} + +[data-theme="dark"] .text-danger { + color: #f87171 !important; +} + +[data-theme="dark"] .text-white { + color: #fff !important; +} + +/* --- Navbar --- */ +[data-theme="dark"] #fwf-top-bar { + background-color: #18181b; + border-bottom: 1px solid #27272a; +} + +[data-theme="dark"] .navbar-brand { + color: #fff !important; } [data-theme="dark"] .nav-link { - color: rgba(255, 255, 255, 0.75) !important; + color: rgba(255, 255, 255, 0.7) !important; } [data-theme="dark"] .nav-link:hover { color: rgba(255, 255, 255, 1) !important; } -/* Pagination */ +[data-theme="dark"] .navbar-text { + color: rgba(255, 255, 255, 0.5) !important; +} + +/* --- Pagination --- */ [data-theme="dark"] .page-link { - background-color: #16213e; - border-color: #2a2a4a; - color: #6cb4ee; + background-color: #1c1c1e; + border-color: #2c2c2e; + color: #60a5fa; } [data-theme="dark"] .page-link:hover { - background-color: #1a1a3e; - border-color: #3a3a5a; - color: #9ccdf5; + background-color: #27272a; + border-color: #3f3f46; + color: #93c5fd; } [data-theme="dark"] .page-item.active .page-link { - background-color: #714a94; - border-color: #714a94; + background-color: #2563eb; + border-color: #2563eb; color: #fff; } [data-theme="dark"] .page-item.disabled .page-link { - background-color: #111; - border-color: #2a2a4a; - color: #555; + background-color: #18181b; + border-color: #27272a; + color: #52525b; } -/* Navbar brand link - keep white */ -[data-theme="dark"] .navbar-brand { - color: #fff !important; +/* --- Danger Zone --- */ +[data-theme="dark"] #danger_zone .card-header { + background-color: #450a0a; + border-bottom-color: #7f1d1d; + color: #fca5a5; } -/* Container text */ -[data-theme="dark"] h1, [data-theme="dark"] h2, -[data-theme="dark"] h3, [data-theme="dark"] h4, -[data-theme="dark"] h5, [data-theme="dark"] h6 { - color: #e0e0e0; +/* --- Close Button --- */ +[data-theme="dark"] .close { + color: #a1a1aa; + text-shadow: none; + opacity: 0.7; } -[data-theme="dark"] code { - color: #d4b5ef; - background-color: #1a1a3e; +[data-theme="dark"] .close:hover { + color: #e4e4e7; + opacity: 1; } -/* Custom select / dropdown */ -[data-theme="dark"] select.form-control { - background-color: #0f3460; - color: #e0e0e0; +/* --- Horizontal Rules --- */ +[data-theme="dark"] hr { + border-top-color: #2c2c2e; } -/* Danger zone card override */ -[data-theme="dark"] #danger_zone .card-header { - background-color: #3d1111; - color: #f5a5a5; +/* --- Scrollbar (Webkit) --- */ +[data-theme="dark"] ::-webkit-scrollbar { + width: 8px; + height: 8px; } -/* Close button in alerts */ -[data-theme="dark"] .close { - color: #e0e0e0; - text-shadow: none; +[data-theme="dark"] ::-webkit-scrollbar-track { + background: #18181b; +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb { + background: #3f3f46; + border-radius: 4px; +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { + background: #52525b; } diff --git a/test/fun_with_flags/ui/utils_test.exs b/test/fun_with_flags/ui/utils_test.exs index 742a966..2063cca 100644 --- a/test/fun_with_flags/ui/utils_test.exs +++ b/test/fun_with_flags/ui/utils_test.exs @@ -52,7 +52,7 @@ defmodule FunWithFlags.UI.UtilsTest do describe "sort_flags(flags)" do - test "it sorts the flag by status, then by name" do + test "it sorts the flags by name" do a = %Flag{name: :aaa, gates: [Gate.new(:group, :people, true)]} b = %Flag{name: :bbb, gates: []} c = %Flag{name: :ccc, gates: [Gate.new(:group, :people, true)]} @@ -64,7 +64,7 @@ defmodule FunWithFlags.UI.UtilsTest do i = %Flag{name: :iii, gates: [Gate.new(:boolean, true)]} input = [i, h, g, f, e, d, c, b, a] - output = [d, g, i, a, c, f, b, e, h] + output = [a, b, c, d, e, f, g, h, i] assert ^output = Utils.sort_flags(input) end
namestatus + name + + status + gates + created +
"> <%= html_escape(flag.name) %> @@ -50,6 +59,10 @@ <%= html_gate_list(flag) %> + 🦖 +