From e0787705573fcb90d4230424d5fdb26f0e04b73a Mon Sep 17 00:00:00 2001 From: Schuwi Date: Thu, 18 Sep 2025 00:08:32 +0200 Subject: [PATCH] refactor(elixir): remove unused qr_code --- APRILTAG_MIGRATION.md | 79 ----------- lib/components_elixir/qr_code.ex | 233 ------------------------------- mix.exs | 1 - 3 files changed, 313 deletions(-) delete mode 100644 APRILTAG_MIGRATION.md delete mode 100644 lib/components_elixir/qr_code.ex diff --git a/APRILTAG_MIGRATION.md b/APRILTAG_MIGRATION.md deleted file mode 100644 index be88411..0000000 --- a/APRILTAG_MIGRATION.md +++ /dev/null @@ -1,79 +0,0 @@ -# AprilTag Migration Summary - -## Completed Changes - -### 1. Database Migration ✅ -- Migrated from `qr_code` string field to `apriltag_id` integer field -- Added constraint to ensure valid AprilTag IDs (0-586) -- Created unique index for apriltag_id -- Preserved old qr_code data as qr_code_old for rollback safety - -### 2. Schema Updates ✅ -- Updated `StorageLocation` schema to use `apriltag_id` instead of `qr_code` -- Added validation for AprilTag ID range (0-586) -- Implemented auto-assignment of next available ID -- Added unique constraint validation - -### 3. Business Logic Refactoring ✅ -- Replaced `ComponentsElixir.QRCode` module with `ComponentsElixir.AprilTag` module -- Updated inventory functions to use AprilTag IDs instead of QR code strings -- Implemented AprilTag ID availability checking -- Added bulk SVG generation functionality - -### 4. UI/UX Improvements ✅ -- Replaced dropdown with 587 options with better UX: - - Radio buttons for "Auto-assign" vs "Manual selection" - - Number input for specific ID selection when manual mode selected - - Shows available ID count and examples - - Different interface for add vs edit forms -- Updated templates to show AprilTag information instead of QR codes -- Added download functionality for AprilTag SVGs - -### 5. AprilTag Generation ✅ -- Created `ComponentsElixir.AprilTag` module for managing tag36h11 family -- Generated all 587 placeholder SVG files with human-readable IDs -- Added Mix task `mix apriltag.generate_all` for batch generation -- SVG files served statically at `/apriltags/tag36h11_id_XXX.svg` - -### 6. Event Handling ✅ -- Updated LiveView event handlers for AprilTag scanning/assignment -- Added mode switching for manual vs automatic assignment -- Implemented proper form state management for different modes - -## Benefits Achieved - -1. **Better UX**: No more 587-option dropdown menu -2. **Future-Ready**: AprilTags designed for multi-tag detection scenarios -3. **Robust**: 587 unique IDs provide ample space without conflicts -4. **Maintainable**: Simpler integer ID system vs complex string encoding -5. **Industry Standard**: AprilTags widely used in robotics/AR applications - -## Current State - -- ✅ Database schema updated -- ✅ All 587 placeholder SVG files generated -- ✅ UI forms updated with better UX -- ✅ Business logic migrated to AprilTag system -- ⏳ **Next**: Real AprilTag pattern generation (future enhancement) -- ⏳ **Next**: Camera detection integration (future enhancement) - -## Usage - -### Generate AprilTag SVGs -```bash -mix apriltag.generate_all # Generate missing files -mix apriltag.generate_all --force # Regenerate all files -``` - -### Available AprilTag IDs -- Range: 0-586 (tag36h11 family) -- Auto-assignment picks next available ID -- Manual assignment allows specific ID selection -- Unique constraint prevents conflicts - -### File Locations -- SVG files: `priv/static/apriltags/tag36h11_id_XXX.svg` -- URL pattern: `/apriltags/tag36h11_id_XXX.svg` -- Placeholder pattern includes human-readable ID label - -The system is now ready for use with AprilTags instead of QR codes! The placeholder SVGs will work perfectly for testing and development until we implement actual AprilTag pattern generation. \ No newline at end of file diff --git a/lib/components_elixir/qr_code.ex b/lib/components_elixir/qr_code.ex deleted file mode 100644 index f75df9a..0000000 --- a/lib/components_elixir/qr_code.ex +++ /dev/null @@ -1,233 +0,0 @@ -defmodule ComponentsElixir.QRCode do - @moduledoc """ - QR Code generation and parsing for storage locations. - - Provides functionality to generate QR codes for storage locations - and parse them back to retrieve location information. - """ - - @doc """ - Generates a QR code data string for a storage location. - - Format: SL:{level}:{qr_code}:{parent_qr_or_ROOT} - - ## Examples - - iex> location = %StorageLocation{level: 1, qr_code: "ABC123", parent: nil} - iex> ComponentsElixir.QRCode.generate_qr_data(location) - "SL:1:ABC123:ROOT" - - iex> parent = %StorageLocation{qr_code: "SHELF1"} - iex> drawer = %StorageLocation{level: 2, qr_code: "DRAW01", parent: parent} - iex> ComponentsElixir.QRCode.generate_qr_data(drawer) - "SL:2:DRAW01:SHELF1" - """ - def generate_qr_data(storage_location) do - parent_code = - case storage_location.parent do - nil -> "ROOT" - parent -> parent.qr_code - end - - "SL:#{storage_location.level}:#{storage_location.qr_code}:#{parent_code}" - end - - @doc """ - Parses a QR code string and extracts components. - - ## Examples - - iex> ComponentsElixir.QRCode.parse_qr_data("SL:1:ABC123:ROOT") - {:ok, %{level: 1, code: "ABC123", parent: "ROOT"}} - - iex> ComponentsElixir.QRCode.parse_qr_data("invalid") - {:error, :invalid_format} - """ - def parse_qr_data(qr_string) do - case String.split(qr_string, ":") do - ["SL", level_str, code, parent] -> - case Integer.parse(level_str) do - {level, ""} -> - {:ok, %{level: level, code: code, parent: parent}} - _ -> - {:error, :invalid_level} - end - _ -> - {:error, :invalid_format} - end - end - - @doc """ - Validates if a string looks like a storage location QR code. - - ## Examples - - iex> ComponentsElixir.QRCode.valid_storage_qr?("SL:1:ABC123:ROOT") - true - - iex> ComponentsElixir.QRCode.valid_storage_qr?("COMP:12345") - false - """ - def valid_storage_qr?(qr_string) do - case parse_qr_data(qr_string) do - {:ok, _} -> true - _ -> false - end - end - - @doc """ - Generates a printable label data structure for a storage location. - - This could be used to generate PDF labels or send to a label printer. - """ - def generate_label_data(storage_location) do - qr_data = generate_qr_data(storage_location) - - %{ - qr_code: qr_data, - name: storage_location.name, - path: storage_location.path, - level: storage_location.level, - description: storage_location.description - } - end - - @doc """ - Generates multiple QR codes for disambiguation testing. - - This is useful for testing multi-QR detection scenarios. - """ - def generate_test_codes(storage_locations) when is_list(storage_locations) do - Enum.map(storage_locations, &generate_qr_data/1) - end - - @doc """ - Generates a QR code image (PNG) for a storage location. - - Returns the binary PNG data that can be saved to disk or served directly. - - ## Options - - - `:size` - The size of the QR code image in pixels (default: 200) - - `:background` - Background color as `{r, g, b}` tuple (default: white) - - `:foreground` - Foreground color as `{r, g, b}` tuple (default: black) - - ## Examples - - iex> location = %StorageLocation{level: 1, qr_code: "ABC123"} - iex> {:ok, png_data} = ComponentsElixir.QRCode.generate_qr_image(location) - iex> File.write!("/tmp/qr_code.png", png_data) - - """ - def generate_qr_image(storage_location, _opts \\ []) do - qr_data = generate_qr_data(storage_location) - - qr_data - |> QRCode.create() - |> QRCode.render(:png) - end - - @doc """ - Generates and saves a QR code image to the specified file path. - - ## Examples - - iex> location = %StorageLocation{level: 1, qr_code: "ABC123"} - iex> ComponentsElixir.QRCode.save_qr_image(location, "/tmp/qr_code.png") - :ok - - """ - def save_qr_image(storage_location, file_path, opts \\ []) do - case generate_qr_image(storage_location, opts) do - {:ok, png_data} -> - # Ensure directory exists - file_path - |> Path.dirname() - |> File.mkdir_p!() - - File.write!(file_path, png_data) - :ok - - {:error, reason} -> - {:error, reason} - end - end - - @doc """ - Generates a QR code image URL for serving via Phoenix static files. - - This function generates the QR code image and saves it to the static directory, - returning a URL that can be used in templates. - - ## Examples - - iex> location = %StorageLocation{id: 123, qr_code: "ABC123"} - iex> ComponentsElixir.QRCode.get_qr_image_url(location) - "/qr_codes/storage_location_123.png" - - """ - def get_qr_image_url(storage_location, opts \\ []) do - filename = "storage_location_#{storage_location.id}.png" - file_path = Path.join([Application.app_dir(:components_elixir, "priv/static/user_generated/qr_codes"), filename]) - - # Generate and save the image if it doesn't exist or if regeneration is forced - force_regenerate = Keyword.get(opts, :force_regenerate, false) - - if force_regenerate || !File.exists?(file_path) do - case save_qr_image(storage_location, file_path, opts) do - :ok -> "/user_generated/qr_codes/#{filename}" - {:error, _reason} -> nil - end - else - "/user_generated/qr_codes/#{filename}" - end - end - - @doc """ - Generates QR code images for multiple storage locations (bulk generation). - - Returns a list of results indicating success or failure for each location. - - ## Examples - - iex> locations = [location1, location2, location3] - iex> ComponentsElixir.QRCode.bulk_generate_images(locations) - [ - {:ok, "/qr_codes/storage_location_1.png"}, - {:ok, "/qr_codes/storage_location_2.png"}, - {:error, "Failed to generate for location 3"} - ] - - """ - def bulk_generate_images(storage_locations, opts \\ []) do - # Use Task.async_stream for concurrent generation with back-pressure - storage_locations - |> Task.async_stream( - fn location -> - case get_qr_image_url(location, Keyword.put(opts, :force_regenerate, true)) do - nil -> {:error, "Failed to generate QR code for location #{location.id}"} - url -> {:ok, url} - end - end, - timeout: :infinity, - max_concurrency: System.schedulers_online() * 2 - ) - |> Enum.map(fn {:ok, result} -> result end) - end - - @doc """ - Cleans up QR code images for deleted storage locations. - - Should be called when storage locations are deleted to prevent orphaned files. - """ - def cleanup_qr_image(storage_location_id) do - filename = "storage_location_#{storage_location_id}.png" - file_path = Path.join([Application.app_dir(:components_elixir, "priv/static/user_generated/qr_codes"), filename]) - - if File.exists?(file_path) do - File.rm(file_path) - else - :ok - end - end -end diff --git a/mix.exs b/mix.exs index f1cc1b4..ef15edf 100644 --- a/mix.exs +++ b/mix.exs @@ -60,7 +60,6 @@ defmodule ComponentsElixir.MixProject do depth: 1}, {:swoosh, "~> 1.16"}, {:req, "~> 0.5"}, - {:qr_code, "~> 3.1"}, {:telemetry_metrics, "~> 1.0"}, {:telemetry_poller, "~> 1.0"}, {:gettext, "~> 0.26"},