Skip to content

Reuse copyright/license metadata + "Add this info to all images" in C&L dialog#7983

Open
hatton wants to merge 10 commits into
masterfrom
RecentImageMetadata
Open

Reuse copyright/license metadata + "Add this info to all images" in C&L dialog#7983
hatton wants to merge 10 commits into
masterfrom
RecentImageMetadata

Conversation

@hatton

@hatton hatton commented Jun 19, 2026

Copy link
Copy Markdown
Member

What this does

Adds tools to the Copyright & License dialog for reusing metadata from other images (pull), and causing all other images to get the same metadata as the current image (push).

image
  • Adds Metadata "Shortcuts" chooser — gathers copyright/license metadata from the other images in the book (plus the book's own copyright/license) so the user can apply an existing set with one click instead of retyping. Gathering is progressive (one image at a time) and stops when the dialog closes. The set matching the image's current metadata is shown checked and pinned to the top of the list.
  • "Add this info to all images in this book" button — replaces the old WinForms yes/no "copy to all images?" MessageBox. The dialog stays open and shows a Working… spinner, then a Done confirmation driven by a websocket event when the copy actually finishes.
  • Launch the dialog via the workspace (top window) bundle rather than the page iframe, so saving the metadata (which reloads the page iframe) no longer tears the dialog down.

The reuse chooser is offered only when editing image metadata, not for the book's own copyright/license.

Backend

New read-only endpoints to list the book's creditable image file names and to read a single image's metadata, plus a static ImageApi.GetCreditableImageFileNamesInBook helper.

🤖 Generated with Claude Code

Devin review


This change is Reviewable

@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds metadata reuse tools to the Copyright & License dialog. The main changes are:

  • Shortcut choices for reusing book or image copyright/license metadata.
  • An in-dialog button for applying the current metadata to all book images.
  • Websocket feedback for the apply-to-all workflow.
  • Workspace-level dialog launch so page iframe reloads do not close the dialog.
  • Backend endpoints and image-list helpers for progressive metadata gathering.

Important Files Changed

Filename Overview
src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx Adds the metadata chooser, apply-to-all workflow, and state handling for websocket completion.
src/BloomBrowserUI/bookEdit/copyrightAndLicense/MetadataChooser.tsx Adds progressive gathering and rendering of reusable metadata packages.
src/BloomBrowserUI/bookEdit/copyrightAndLicense/metadataReuseUtils.ts Adds shared helpers for package identity and license shorthand display.
src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs Adds read-only metadata endpoints and apply-to-all handling for image metadata saves.
src/BloomExe/web/controllers/ImageApi.cs Adds a reusable helper for listing creditable image files in book order.

Reviews (8): Last reviewed commit: "Reset stale push "Done" when a reuse sho..." | Re-trigger Greptile

Comment thread src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx Outdated
@hatton hatton marked this pull request as ready for review June 22, 2026 19:55
Copilot AI review requested due to automatic review settings June 22, 2026 19:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the Copyright & License dialog workflow for image metadata editing by adding (1) a progressive “reuse metadata” chooser sourced from other images in the book (and the book’s own C&L), and (2) an in-dialog “Add this info to all images in this book” push action with spinner + websocket-driven completion. It also adjusts how the dialog is launched so saving metadata (which reloads the page iframe) no longer tears the dialog down.

Changes:

  • Added backend support for progressively discovering creditable images in a book and reading per-image metadata.
  • Added React UI for metadata “Shortcuts” reuse, plus a push-to-all-images button with spinner/done states driven by a websocket event.
  • Launched the dialog via the workspace (top window) bundle rather than the page iframe to prevent teardown on iframe reload.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/BloomExe/web/controllers/ImageApi.cs Adds a static helper to list creditable (non-placeholder/branding/license) images used in a book.
src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs Adds read-only endpoints for image list + per-image metadata; adds apply-to-all handling and websocket completion signaling.
src/BloomExe/Edit/EditingView.cs Normalizes image filenames from HTML src (decode + strip query) before loading metadata; removes old yes/no “copy to all?” prompt.
src/BloomExe/Edit/EditingModel.cs Adds websocket context/event constants and a helper to notify the dialog that push-to-all finished.
src/BloomBrowserUI/bookEdit/toolbox/canvas/canvasControlRegistry.ts Launches C&L dialog via workspace bundle exports (top window) instead of iframe import.
src/BloomBrowserUI/bookEdit/js/bloomImages.ts Same workspace-root dialog launch adjustment for metadata button on images.
src/BloomBrowserUI/bookEdit/copyrightAndLicense/metadataReuseUtils.ts Adds shared helpers for stable metadata-package identity + license shorthand.
src/BloomBrowserUI/bookEdit/copyrightAndLicense/MetadataChooser.tsx New progressive metadata reuse chooser UI (dedupe, pin current match, render localized summaries).
src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx Adds chooser + push-to-all UI/state management and websocket subscription for completion.
DistFiles/localization/en/BloomMediumPriority.xlf Adds new localization IDs for chooser header/labels and push-to-all/progress text.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs
Comment thread src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx Outdated
Comment thread src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx Outdated
Comment thread src/BloomExe/web/controllers/CopyrightAndLicenseApi.cs
Comment thread src/BloomExe/web/controllers/ImageApi.cs

@JohnThomson JohnThomson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few very minor points. A slightly larger issue: in my current card about the image chooser, we are dropping the idea of locking credits for images from collections like AOR. But would we still want to prevent "copy to all images" from applying to them? Also wondering whether we want to invite users to copy the AOR credits to new images...should we filter them out?

@JohnThomson reviewed 11 files and all commit messages, and made 4 comments.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on hatton).


src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx line 420 at r3 (raw file):

                    enabled={canSave && pushState !== "working"}
                />
                <DialogCancelButton onClick_DEPRECATED={closeDialog} />

What are the implications of canceling while a copy-to-all-images is in-progress?


src/BloomBrowserUI/bookEdit/copyrightAndLicense/MetadataChooser.tsx line 65 at r3 (raw file):

    // scroll). With four or more, we measure the first three rows and cap to exactly that
    // height, so the fourth option starts the scrollbar. Measuring (rather than a fixed pixel
    // height) keeps this correct across fonts and translations, where row heights vary.

In some contexts we have purposely shown a half-item, to make it obvious there are more.


src/BloomExe/web/controllers/ImageApi.cs line 107 at r3 (raw file):

        {
            var doNotPaste = new HashSet<string>(
                GetImageFilesToNotPasteCreditsFor().Select(BookStorage.GetNormalizedPathForOS)

What does this have to do with pasting credits? We are listing them in a dialog; I'm not aware of any way to paste credits.

@hatton

hatton commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@
[Claude Opus 4.8] Addressed two of your review points:

MetadataChooser.tsx line 65 (half-item) — Good point. Changed the
height calculation to measure three full rows plus half of the fourth, so
when there are four or more reuse options a peek of the fourth row makes it
obvious the list scrolls (matching the half-item convention used elsewhere).
Commit 6a91792.

ImageApi.cs line 107 (doNotPaste) — Agreed, the "paste" terminology
was a leftover and misleading; this list has nothing to do with pasting.
Renamed GetImageFilesToNotPasteCreditsFor -> GetImageFilesToNotCreditFor
and doNotPaste -> nonCreditableImages, and fixed the comment. No behavior
change. Commit 29cbe43.

Still working on your other two points (cancel-during-copy-to-all
implications, and the larger AOR filtering question).
@

Comment thread src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx Outdated

@hatton hatton left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is based on code before yours, didn't take on that change of behavior.

@hatton partially reviewed 10 files and made 4 comments.
Reviewable status: 9 of 11 files reviewed, 4 unresolved discussions (waiting on JohnThomson).


src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx line 420 at r3 (raw file):

Previously, JohnThomson (John Thomson) wrote…

What are the implications of canceling while a copy-to-all-images is in-progress?

Well one hopes it would stop.


src/BloomBrowserUI/bookEdit/copyrightAndLicense/MetadataChooser.tsx line 65 at r3 (raw file):

Previously, JohnThomson (John Thomson) wrote…

In some contexts we have purposely shown a half-item, to make it obvious there are more.

Done.


src/BloomExe/web/controllers/ImageApi.cs line 107 at r3 (raw file):

Previously, JohnThomson (John Thomson) wrote…

What does this have to do with pasting credits? We are listing them in a dialog; I'm not aware of any way to paste credits.

Yeah that's vestigial. I'll get rid of it.

hatton and others added 8 commits June 25, 2026 17:26
…&L dialog

Reworks the Copyright & License dialog for images:

- Adds a "Shortcuts" reuse chooser that gathers copyright/license metadata
  from the other images in the book (plus the book's own copyright/license)
  so the user can apply an existing set with one click instead of retyping.
  Gathering is progressive (one image at a time) and stops when the dialog
  closes. The set matching the image's current metadata is shown checked and
  pinned to the top of the list.

- Replaces the old WinForms yes/no "copy to all images?" MessageBox with an
  in-dialog "Add this info to all images in this book" button. The dialog
  stays open and shows a "Working…" spinner, then a "done" confirmation
  driven by a websocket event when the copy actually finishes.

- Launches the dialog via the workspace (top window) bundle rather than the
  page iframe, so saving the metadata (which reloads the page iframe) no
  longer tears the dialog down.

- BL-16446: reduces the image fileName (which may carry a query string and/or
  URL encoding from the html src attribute) to the on-disk file name before
  loading, so PalasoImage no longer wrongly reports the image as corrupt.

Backend: new read-only endpoints to list the book's creditable image file
names and to read a single image's metadata, plus a static
ImageApi.GetCreditableImageFileNamesInBook helper.

The reuse chooser is only offered when editing image metadata, not for the
book's own copyright/license.

Robustness: the image-metadata POST handler now notifies the dialog that an
"add to all images" request has finished on every path (via a finally and the
wrong-state branch), so the "Working…" spinner can never hang when the copy
doesn't actually run (save failed, non-normal image, or an error).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vy5zgbKXxtYoMeaUxWBhA8
…els)

The gather useEffect ran once on mount and baked localized labels into each
package's summary/secondaryLabel. But useL10n returns the English fallback
synchronously and the localized string only on a later render, so those
display strings were stuck in English (non-CC license shorthands and the
"Copyright and license from this book" label).

Store only raw data in each package (plus an isFromThisBook flag) and compute
the summary and the book label at render time, where the labels are always the
current, localized ones. The gather effect no longer captures any label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vy5zgbKXxtYoMeaUxWBhA8
When the Copyright & License dialog is launched from the Palaso image
toolbox, the POST handler early-returns before reaching the SaveThen
block that calls NotifyCopyrightPushedToAllImages(). The "Add this info
to all images" button is still visible in that case, so clicking it left
the dialog spinner stuck on "Working…" forever. Now fire the
notification in the toolbox early-return path to clear the spinner.

Also disable the OK button while a push is in flight (so the dialog
can't unmount before the "pushedToAllImages" event arrives) and add a
clarifying comment about path traversal in HandleImageMetadataForFile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
handlePushToAllImages relied entirely on the "pushedToAllImages" websocket
event to leave the "working" state. If the POST itself failed before
reaching the C# server, no event would arrive and the spinner would hang
indefinitely. Add an errorCallback that resets pushState to "idle".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mages helper

- CopyrightAndLicenseDialog: editing a field while a push-to-all is still
  "working" no longer resets pushState to "idle". Resetting mid-operation
  would hide the spinner and re-enable the button for redundant clicks. We
  keep the spinner until the websocket completion event arrives.
- Add a focused unit test for ImageApi.GetCreditableImageFileNamesInBook
  locking down ordering (first occurrence) and filtering of license and
  placeholder images.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Show half-row peek in reuse chooser to signal scrolling

Per review: when there are four or more reuse options, measure three full
rows plus half of the fourth (rather than capping exactly at three) so a
peek of the fourth row makes it obvious the list scrolls. This matches the
convention used elsewhere in Bloom of showing a half-item to indicate more.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
Rename doNotPaste->nonCreditable in image-credit filtering

Per review: the "paste" terminology was misleading—this list has nothing to
do with pasting credits; it is the set of images we never generate/show
credits for (license, placeholder, branding). Rename
GetImageFilesToNotPasteCreditsFor->GetImageFilesToNotCreditFor and the local
doNotPaste->nonCreditableImages, and fix the comment accordingly. No
behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
Drop stale "unless unlocked" wording from C&L launcher comment

We are not implementing the idea of locking credits for images from
collections like AOR, so the launcher comment no longer describes a real
code path. Replace the "read-only / cannot be changed unless unlocked"
wording with an accurate description of what the GET actually does: return
the image data (show the dialog), return nothing, or fail with a message we
surface (e.g. embedded-data images cannot be edited).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
@hatton hatton force-pushed the RecentImageMetadata branch from 29cbe43 to 0392501 Compare June 25, 2026 23:27
@hatton

hatton commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@
[Claude Opus 4.8] On the locking-credits question: we are dropping the whole
idea of locking credits for images from collections like AOR, so the code now
treats every creditable image uniformly — no AOR/collection special-casing.

Concretely:

  • "Copy to all images" applies to all creditable images (everything except
    license, placeholder, and branding images), AOR included. We are not adding a
    guard to skip collection images.
  • The reuse chooser likewise offers AOR copyright/license packages as
    candidates; we do not filter them out. They are legitimate sets to reuse, and
    with locking gone there is no reason to hide them.

I also rebased the branch onto current master (where the locking idea is gone)
and adapted the one stale artifact that referenced it: the comment on
showCopyrightAndLicenseInfoOrDialog in CopyrightAndLicenseDialog.tsx used to
mention "read-only / cannot be changed unless unlocked," a path that does not
exist in the code. It now accurately describes what the GET does (return the
image data, return nothing, or fail with a surfaced message such as embedded-data
images not being editable).

Force-pushed the rebased branch (tip 0392501).
@

Addresses PR review (greptile P1): if the user clicks "Add this info to all
images" and then edits the copyright/license while the push is still running,
markEditedSincePush keeps pushState at "working" (so the spinner stays and the
button cannot be re-clicked). But the completion websocket event then flipped
the UI to "done" unconditionally, claiming the current, edited values had been
applied to all images when only the earlier values were.

Tie completion to the submitted values: remember the package key of the values
pushed, and when "pushedToAllImages" arrives, show "done" only if the dialog's
current values still match that key; otherwise drop back to "idle" so the
button is offered again for the new, un-pushed values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picking a metadata reuse shortcut runs applyMetadataPackage(), which
changed the copyright/license fields without clearing a prior "pushed to
all images" confirmation. A user could click "Add this info to all
images", wait for "Done", then pick a different shortcut and still see
"Done" for values that were never applied to all images.

Route the shortcut path through the same markEditedSincePush() helper the
manual-edit handlers use, so it re-offers the button (and leaves an
in-flight push's spinner alone).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@hatton hatton left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hatton made 1 comment.
Reviewable status: 3 of 11 files reviewed, 3 unresolved discussions (waiting on JohnThomson).


src/BloomBrowserUI/bookEdit/copyrightAndLicense/CopyrightAndLicenseDialog.tsx line 420 at r3 (raw file):

Previously, hatton (John Hatton) wrote…

Well one hopes it would stop.

Canceling mid-copy is safe — the copy was already initiated server-side and completes; closing the dialog only stops showing progress. No abort, no partial state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants