Claude set up a '/labels' endpoint
This commit is contained in:
38
lib/labelmaker_web/controllers/labels_controller.ex
Normal file
38
lib/labelmaker_web/controllers/labels_controller.ex
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
defmodule LabelmakerWeb.LabelsController do
|
||||||
|
use LabelmakerWeb, :controller
|
||||||
|
|
||||||
|
@label_dir Path.join(:code.priv_dir(:labelmaker), "static/labels")
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
labels = list_labels()
|
||||||
|
render(conn, :index, labels: labels)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp list_labels do
|
||||||
|
case File.ls(@label_dir) do
|
||||||
|
{:ok, files} ->
|
||||||
|
files
|
||||||
|
|> Enum.filter(&String.ends_with?(&1, ".png"))
|
||||||
|
|> Enum.map(fn filename ->
|
||||||
|
filepath = Path.join(@label_dir, filename)
|
||||||
|
stat = File.stat!(filepath)
|
||||||
|
|
||||||
|
%{
|
||||||
|
filename: filename,
|
||||||
|
filepath: filepath,
|
||||||
|
size: stat.size,
|
||||||
|
modified: stat.mtime,
|
||||||
|
url: "/labels/#{filename}"
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|> Enum.sort_by(& &1.modified, :desc)
|
||||||
|
|
||||||
|
{:error, :enoent} ->
|
||||||
|
# Directory doesn't exist yet
|
||||||
|
[]
|
||||||
|
|
||||||
|
{:error, _reason} ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
lib/labelmaker_web/controllers/labels_html.ex
Normal file
16
lib/labelmaker_web/controllers/labels_html.ex
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
defmodule LabelmakerWeb.LabelsHTML do
|
||||||
|
use LabelmakerWeb, :html
|
||||||
|
|
||||||
|
embed_templates "labels_html/*"
|
||||||
|
|
||||||
|
def format_size(bytes) when bytes < 1024, do: "#{bytes} B"
|
||||||
|
def format_size(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 1)} KB"
|
||||||
|
def format_size(bytes), do: "#{Float.round(bytes / (1024 * 1024), 1)} MB"
|
||||||
|
|
||||||
|
def format_datetime({{year, month, day}, {hour, minute, second}}) do
|
||||||
|
"#{year}-#{pad(month)}-#{pad(day)} #{pad(hour)}:#{pad(minute)}:#{pad(second)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp pad(num) when num < 10, do: "0#{num}"
|
||||||
|
defp pad(num), do: "#{num}"
|
||||||
|
end
|
||||||
74
lib/labelmaker_web/controllers/labels_html/index.html.heex
Normal file
74
lib/labelmaker_web/controllers/labels_html/index.html.heex
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<div class="max-w-6xl mx-auto p-6">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h1 class="text-4xl font-bold text-primary">Generated Labels</h1>
|
||||||
|
<a href={~p"/"} class="text-primary hover:underline">← Back to Home</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= if Enum.empty?(@labels) do %>
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<p class="text-xl text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
No labels generated yet.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href={~p"/"}
|
||||||
|
class="inline-block bg-primary text-fg-light px-4 py-2 rounded hover:bg-highlight focus:ring-fg-light"
|
||||||
|
>
|
||||||
|
Create Your First Label
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="mb-4 text-gray-600 dark:text-gray-400">
|
||||||
|
Total labels: <%= length(@labels) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<%= for label <- @labels do %>
|
||||||
|
<div class="border border-gray-300 dark:border-gray-600 rounded-lg p-4 bg-secondary-light dark:bg-secondary-dark">
|
||||||
|
<div class="mb-3 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded flex items-center justify-center p-4 min-h-[150px]">
|
||||||
|
<img
|
||||||
|
src={label.url}
|
||||||
|
alt="Label image"
|
||||||
|
class="max-w-full max-h-[200px] object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">Size:</span>
|
||||||
|
<span class="font-medium text-fg-light dark:text-fg-dark">
|
||||||
|
<%= format_size(label.size) %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-gray-600 dark:text-gray-400">Modified:</span>
|
||||||
|
<span class="font-medium text-fg-light dark:text-fg-dark text-xs">
|
||||||
|
<%= format_datetime(label.modified) %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-2 flex gap-2">
|
||||||
|
<a
|
||||||
|
href={label.url}
|
||||||
|
target="_blank"
|
||||||
|
class="flex-1 text-center bg-primary text-fg-light px-3 py-2 rounded text-sm hover:bg-highlight"
|
||||||
|
>
|
||||||
|
View Full Size
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
value={label.url}
|
||||||
|
class="w-full text-xs px-2 py-1 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-fg-light dark:text-fg-dark"
|
||||||
|
onclick="this.select()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -18,6 +18,7 @@ defmodule LabelmakerWeb.Router do
|
|||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
live "/", Home
|
live "/", Home
|
||||||
|
get "/labels", LabelsController, :index
|
||||||
get "/:label", LabelController, :show
|
get "/:label", LabelController, :show
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
93
test/labelmaker_web/controllers/labels_controller_test.exs
Normal file
93
test/labelmaker_web/controllers/labels_controller_test.exs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
defmodule LabelmakerWeb.LabelsControllerTest do
|
||||||
|
use LabelmakerWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
@label_dir Path.join(:code.priv_dir(:labelmaker), "static/labels")
|
||||||
|
|
||||||
|
setup do
|
||||||
|
# Clean up test images before each test
|
||||||
|
File.rm_rf!(@label_dir)
|
||||||
|
File.mkdir_p!(@label_dir)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "renders empty state when no labels exist", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ "No labels generated yet"
|
||||||
|
assert html_response(conn, 200) =~ "Create Your First Label"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "lists all generated labels", %{conn: conn} do
|
||||||
|
# Generate some test labels
|
||||||
|
_conn1 = get(conn, ~p"/TestLabel1?color=red")
|
||||||
|
_conn2 = get(conn, ~p"/TestLabel2?color=blue")
|
||||||
|
_conn3 = get(conn, ~p"/TestLabel3?color=green")
|
||||||
|
|
||||||
|
# Visit labels page
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
html = html_response(conn, 200)
|
||||||
|
|
||||||
|
assert html =~ "Generated Labels"
|
||||||
|
assert html =~ "Total labels: 3"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "displays label information correctly", %{conn: conn} do
|
||||||
|
# Generate a test label
|
||||||
|
_conn = get(conn, ~p"/MyLabel?color=yellow&size=96")
|
||||||
|
|
||||||
|
# Visit labels page
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
html = html_response(conn, 200)
|
||||||
|
|
||||||
|
# Should show size and modified date
|
||||||
|
assert html =~ "Size:"
|
||||||
|
assert html =~ "Modified:"
|
||||||
|
assert html =~ "View Full Size"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows labels sorted by most recent first", %{conn: conn} do
|
||||||
|
# Generate labels with delays to ensure different timestamps
|
||||||
|
_conn1 = get(conn, ~p"/First")
|
||||||
|
Process.sleep(100)
|
||||||
|
_conn2 = get(conn, ~p"/Second")
|
||||||
|
Process.sleep(100)
|
||||||
|
_conn3 = get(conn, ~p"/Third")
|
||||||
|
|
||||||
|
# Visit labels page
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
html = html_response(conn, 200)
|
||||||
|
|
||||||
|
# Should show all labels
|
||||||
|
assert html =~ "Total labels: 3"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles missing labels directory gracefully", %{conn: conn} do
|
||||||
|
# Remove the labels directory entirely
|
||||||
|
File.rm_rf!(@label_dir)
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ "No labels generated yet"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "includes link back to home", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
html = html_response(conn, 200)
|
||||||
|
|
||||||
|
assert html =~ "Back to Home"
|
||||||
|
assert html =~ ~p"/"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows clickable URLs for each label", %{conn: conn} do
|
||||||
|
_conn = get(conn, ~p"/ClickableTest")
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/labels")
|
||||||
|
html = html_response(conn, 200)
|
||||||
|
|
||||||
|
# Should have a URL input field
|
||||||
|
assert html =~ "/labels/"
|
||||||
|
assert html =~ ".png"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user