Create new assets when saving a photo
Instead of selecting existing assets, the user can now type names for
new assets in the capture form. Saving the photo creates each named
asset and links it to the file via asset_attachments in a single
transaction. Blank names are ignored. Alpine.js tracks the list of
name inputs and clears them on discard.
change
commit ec7b72810b1a1c422518fad6f41b4f3d25d77569
author Claude <noreply@anthropic.com>
date
parent 5ed8df76
diff --git a/lib/views/capture.rb b/lib/views/capture.rb
index a1506ad..2fbbfcd 100644
--- a/lib/views/capture.rb
+++ b/lib/views/capture.rb
@@ -7,10 +7,6 @@ module Domus
     class Capture < Phlex::HTML
       ICONS_DIR = File.expand_path("../../public/icons", __dir__)
 
-      def initialize(assets: [])
-        @assets = assets
-      end
-
       def view_template
         doctype
         html(lang: "en") do
@@ -123,16 +119,28 @@ module Domus
               end
 
               div(class: "save-form") do
-                unless @assets.empty?
-                  div(class: "asset-list") do
-                    p(class: "asset-list-label") { plain "Add to assets (optional)" }
-                    @assets.each do |asset|
-                      label(class: "asset-item") do
-                        input(type: "checkbox", name: "asset_ids[]", value: asset[:id])
-                        plain asset[:name]
-                      end
+                div(class: "asset-inputs") do
+                  p(class: "asset-inputs-label") { plain "Create assets (optional)" }
+                  template("x-for": "(_, i) in assetNames", ":key": "i") do
+                    div(class: "asset-input-row") do
+                      input(
+                        type: "text",
+                        name: "asset_names[]",
+                        placeholder: "Asset name",
+                        "@input": "assetNames[i] = $event.target.value"
+                      )
+                      button(
+                        type: "button",
+                        class: "btn-remove-asset",
+                        "@click": "assetNames.splice(i, 1)"
+                      ) { plain "×" }
                     end
                   end
+                  button(
+                    type: "button",
+                    class: "btn btn-ghost btn-small",
+                    "@click": "assetNames.push('')"
+                  ) { plain "+ Add asset" }
                 end
 
                 div(class: "btn-row") do
diff --git a/lib/web.rb b/lib/web.rb
index ad1a1a9..f1cff2c 100644
--- a/lib/web.rb
+++ b/lib/web.rb
@@ -35,8 +35,7 @@ module Domus
 
       r.root do
         r.get do
-          assets = db[:assets].order(:name).all
-          Views::Capture.new(assets:).call
+          Views::Capture.new.call
         end
       end
 
@@ -66,7 +65,7 @@ module Domus
       raise ClientError, "That image format isn't supported." unless IMAGE_EXTENSIONS.include?(ext)
       raise ClientError, "That image is too large (25 MB max)." if upload[:tempfile].size > MAX_UPLOAD_BYTES
 
-      asset_ids = Array(params["asset_ids"]).flatten.map(&:to_i).reject(&:zero?)
+      asset_names = Array(params["asset_names"]).flatten.map(&:strip).reject(&:empty?)
 
       db.transaction do
         file_id = db[:files].insert(extension: ext, created_at: Time.now)
@@ -76,7 +75,8 @@ module Domus
         FileUtils.cp(upload[:tempfile].path, dest)
 
         now = Time.now
-        asset_ids.each do |asset_id|
+        asset_names.each do |name|
+          asset_id = db[:assets].insert(name: name, created_at: now)
           db[:asset_attachments].insert(asset_id: asset_id, file_id: file_id, created_at: now)
         end
       end
diff --git a/public/capture.js b/public/capture.js
index 311a547..ee7a55f 100644
--- a/public/capture.js
+++ b/public/capture.js
@@ -4,6 +4,7 @@ function captureApp() {
     preview: null,
     dragging: false,
     activeRef: null,
+    assetNames: [],
 
     handleFile(file, ref) {
       if (!file) return;
@@ -34,6 +35,7 @@ function captureApp() {
       this.state = 'capture';
       this.preview = null;
       this.activeRef = null;
+      this.assetNames = [];
       this.$refs.fileInput.disabled = false;
       this.$refs.cameraInput.disabled = false;
       this.$refs.fileInput.value = '';
diff --git a/test/test_app.rb b/test/test_app.rb
index 14b9873..dd1fd41 100644
--- a/test/test_app.rb
+++ b/test/test_app.rb
@@ -65,43 +65,43 @@ class TestApp < Minitest::Test
     assert_equal 0, domus.db[:files].count
   end
 
-  def test_root_shows_assets
-    domus.db[:assets].insert(name: "Camera", created_at: Time.now)
-
-    get "/"
-
-    assert_equal 200, last_response.status
-    assert_includes last_response.body, "Camera"
-  end
-
-  def test_upload_with_asset_ids_creates_attachments
-    asset_id = domus.db[:assets].insert(name: "Laptop", created_at: Time.now)
-
-    post "/files", "file" => upload("photo.png", "image/png", "bytes"), "asset_ids[]" => asset_id.to_s
+  def test_upload_with_asset_name_creates_asset_and_attachment
+    post "/files", "file" => upload("photo.png", "image/png", "bytes"), "asset_names[]" => "Laptop"
 
     assert_equal 302, last_response.status
+    asset = domus.db[:assets].first
+    refute_nil asset
+    assert_equal "Laptop", asset[:name]
     file = domus.db[:files].first
     attachment = domus.db[:asset_attachments].first
     refute_nil attachment
-    assert_equal asset_id, attachment[:asset_id]
+    assert_equal asset[:id], attachment[:asset_id]
     assert_equal file[:id], attachment[:file_id]
   end
 
-  def test_upload_with_multiple_asset_ids_creates_all_attachments
-    id1 = domus.db[:assets].insert(name: "Camera", created_at: Time.now)
-    id2 = domus.db[:assets].insert(name: "Laptop", created_at: Time.now)
-
+  def test_upload_with_multiple_asset_names_creates_all
     post "/files", "file" => upload("photo.png", "image/png", "bytes"),
-      "asset_ids[]" => [id1.to_s, id2.to_s]
+      "asset_names[]" => ["Camera", "Laptop"]
 
     assert_equal 302, last_response.status
+    assert_equal 2, domus.db[:assets].count
     assert_equal 2, domus.db[:asset_attachments].count
   end
 
-  def test_upload_without_asset_ids_creates_no_attachments
+  def test_upload_with_blank_asset_names_ignored
+    post "/files", "file" => upload("photo.png", "image/png", "bytes"),
+      "asset_names[]" => ["", "  "]
+
+    assert_equal 302, last_response.status
+    assert_equal 0, domus.db[:assets].count
+    assert_equal 0, domus.db[:asset_attachments].count
+  end
+
+  def test_upload_without_asset_names_creates_no_assets
     post "/files", "file" => upload("photo.png", "image/png", "bytes")
 
     assert_equal 302, last_response.status
+    assert_equal 0, domus.db[:assets].count
     assert_equal 0, domus.db[:asset_attachments].count
   end