diff --git a/eslint.config.mjs b/eslint.config.mjs index c9fd7abb6e7..f86d22dbf7a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -980,6 +980,9 @@ export default [ 'root/search/components/WorkResults.js', 'root/static/scripts/account/components/EditProfileForm.js', 'root/static/scripts/account/components/RegisterForm.js', + 'root/static/scripts/common/components/CDTocMediumListRow.js', + 'root/static/scripts/common/components/CDTocPossibleMediumListRow.js', + 'root/static/scripts/common/components/CDTocReleaseListRow.js', 'root/static/scripts/common/components/TagEditor.js', 'root/static/scripts/edit/check-duplicates.js', 'root/static/scripts/edit/components/ExternalLinkAttributeDialog.js', diff --git a/lib/MusicBrainz/Server/Controller/CDTOC.pm b/lib/MusicBrainz/Server/Controller/CDTOC.pm index 66c282d5b78..5a9966abcd4 100644 --- a/lib/MusicBrainz/Server/Controller/CDTOC.pm +++ b/lib/MusicBrainz/Server/Controller/CDTOC.pm @@ -65,9 +65,15 @@ sub show : Chained('load') PathPart('') my $cdtoc = $c->stash->{cdtoc}; my $medium_cdtocs = $self->_load_releases($c, $cdtoc); + my %props = ( + mediumCDTocs => to_json_array($medium_cdtocs), + cdToc => $cdtoc->TO_JSON, + ); + $c->stash( - medium_cdtocs => $medium_cdtocs, - template => 'cdtoc/index.tt', + current_view => 'Node', + component_path => 'cdtoc/CDTocIndex.js', + component_props => \%props, ); } @@ -287,6 +293,7 @@ sub _attach_list { $c->model('Release')->find_for_cdtoc($artist_id, $cdtoc->track_count, shift, shift); }); $c->model('Release')->load_related_info(@$releases); + $c->model('ArtistCredit')->load(@$releases); my @mediums = grep { !$_->format || $_->format->has_discids } map { $_->all_mediums } @$releases; @@ -298,11 +305,20 @@ sub _attach_list { my @rgs = $c->model('ReleaseGroup')->load(@$releases); $c->model('ReleaseGroup')->load_meta(@rgs); + my %props = ( + artist => $artist->TO_JSON, + cdToc => $cdtoc->TO_JSON, + pager => serialize_pager($c->stash->{pager}), + releases => to_json_array($releases), + tocString => $c->stash->{toc}, + ); + $c->stash( - artist => $artist, - releases => $releases, - template => 'cdtoc/attach_artist_releases.tt', + current_view => 'Node', + component_path => 'cdtoc/AttachCDTocToArtistRelease.js', + component_props => \%props, ); + $c->detach; } else { my $search_artist = $c->form( query_artist => 'Search::Query', name => 'filter-artist' ); @@ -311,6 +327,8 @@ sub _attach_list { my ($initial_artist, $initial_release) = map { $c->req->query_params->{$_} } qw( artist-name release-name ); + my $cdstub; + my @possible_mediums; # One of these must have been submitted to get here if ($c->form_submitted_and_valid($search_artist, $c->req->query_params)) { my $artists = $self->_load_paged($c, sub { @@ -393,9 +411,10 @@ sub _attach_list { component_path => 'cdtoc/AttachCDTocToRelease.js', component_props => \%props, ); + $c->detach; } else { - my $cdstub = $c->model('CDStub')->get_by_discid($cdtoc->discid); + $cdstub = $c->model('CDStub')->get_by_discid($cdtoc->discid); if ($cdstub) { $c->model('CDStubTrack')->load_for_cdstub($cdstub); $cdstub->update_track_lengths; @@ -403,16 +422,16 @@ sub _attach_list { $initial_artist ||= $cdstub->artist; $initial_release ||= $cdstub->title; - my @mediums = $c->model('Medium')->find_for_cdstub($cdstub); - $c->model('MediumFormat')->load(@mediums); - $c->model('Track')->load_for_mediums(@mediums); - my @tracks = map { $_->all_tracks } @mediums; + @possible_mediums = $c->model('Medium')->find_for_cdstub($cdstub); + $c->model('MediumFormat')->load(@possible_mediums); + $c->model('Track')->load_for_mediums(@possible_mediums); + my @tracks = map { $_->all_tracks } @possible_mediums; $c->model('Recording')->load(@tracks); - my @releases = map { $_->release } @mediums; + my @releases = map { $_->release } @possible_mediums; $c->model('Release')->load_related_info(@releases); $c->model('ArtistCredit')->load(@releases); $c->stash( - possible_mediums => [ @mediums ], + possible_mediums => [ @possible_mediums ], cdstub => $cdstub, ); } @@ -424,10 +443,22 @@ sub _attach_list { $search_release->process(params => { 'filter-release.query' => $initial_release }) if $initial_release; + my $medium_cdtocs = $self->_load_releases($c, $cdtoc); + + my %props = ( + $cdstub ? (cdStub => $cdstub->TO_JSON) : (), + cdToc => $cdtoc->TO_JSON, + mediumCDTocs => to_json_array($medium_cdtocs), + possibleMediums => to_json_array(\@possible_mediums), + searchArtistForm => $search_artist->TO_JSON, + searchReleaseForm => $search_release->TO_JSON, + tocString => $c->stash->{toc}, + ); + $c->stash( - medium_cdtocs => $self->_load_releases($c, $cdtoc), - cdtoc => $cdtoc, - template => 'cdtoc/lookup.tt', + current_view => 'Node', + component_path => 'cdtoc/CDTocLookup.js', + component_props => \%props, ); } } diff --git a/root/cdtoc/AttachCDTocToArtistRelease.js b/root/cdtoc/AttachCDTocToArtistRelease.js new file mode 100644 index 00000000000..a5393d745c5 --- /dev/null +++ b/root/cdtoc/AttachCDTocToArtistRelease.js @@ -0,0 +1,108 @@ +/* + * @flow strict + * Copyright (C) 2025 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import * as React from 'react'; + +import PaginatedResults from '../components/PaginatedResults.js'; +import Layout from '../layout/index.js'; +import manifest from '../static/manifest.mjs'; +import CDTocArtistReleaseListTable + from '../static/scripts/common/components/CDTocArtistReleaseListTable.js'; +import EntityLink from '../static/scripts/common/components/EntityLink.js'; +import FormSubmit from '../static/scripts/edit/components/FormSubmit.js'; +import type {ReleaseWithMediumsAndReleaseGroupT} + from '../static/scripts/relationship-editor/types.js'; + +component AttachCDTocToArtistRelease( + artist: ArtistT, + cdToc: CDTocT, + pager?: PagerT, + releases: $ReadOnlyArray, + tocString: StrOrNum, +) { + const title = lp('Attach CD TOC', 'header'); + const cdTocTrackCount = cdToc.track_count; + + return ( + +

{title}

+ +

+ {exp.l( + 'You are viewing releases by {artist}.', + {artist: }, + )} +

+ + {releases.length > 0 ? ( + <> +

+ {l('Please select the medium you wish to attach this CD TOC to.')} +

+ +
+ + + + {pager ? ( + + + {manifest( + 'common/components/CDTocArtistReleaseListTable', + {async: true}, + )} + {manifest( + 'common/components/ReleaseEvents', + {async: true}, + )} + + ) : null} +

+ +

+
+ + ) : ( +

+ {exp.ln( + '{artist} has no releases which have only {n} track.', + '{artist} has no releases which have {n} tracks.', + cdTocTrackCount, + {artist: , n: cdTocTrackCount}, + )} +

+ )} + +

{l('Add a new release')}

+

+ {l(`If you don't see the release you are looking for, + you can still add a new one, using this CD TOC:`)} +

+ +
+ + + + +
+ ); +} + +export default AttachCDTocToArtistRelease; diff --git a/root/cdtoc/CDTocIndex.js b/root/cdtoc/CDTocIndex.js new file mode 100644 index 00000000000..538b871b72b --- /dev/null +++ b/root/cdtoc/CDTocIndex.js @@ -0,0 +1,56 @@ +/* + * @flow strict + * Copyright (C) 2025 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import Layout from '../layout/index.js'; +import manifest from '../static/manifest.mjs'; +import CDTocLink from '../static/scripts/common/components/CDTocLink.js'; +import CDTocMediumListTable + from '../static/scripts/common/components/CDTocMediumListTable.js'; +import linkedEntities from '../static/scripts/common/linkedEntities.mjs'; + +import CDTocInfo from './CDTocInfo.js'; + +component CDTocIndex( + cdToc: CDTocT, + mediumCDTocs: $ReadOnlyArray, +) { + return ( + +

+ {exp.l( + 'Disc ID “{discid}”', + {discid: }, + )} +

+ + + +

{l('Attached to releases')}

+ + {manifest( + 'common/components/CDTocMediumListTable', + {async: true}, + )} + {manifest( + 'common/components/ReleaseEvents', + {async: true}, + )} + +
+ ); +} + +export default CDTocIndex; diff --git a/root/cdtoc/CDTocLookup.js b/root/cdtoc/CDTocLookup.js new file mode 100644 index 00000000000..5a05b61e758 --- /dev/null +++ b/root/cdtoc/CDTocLookup.js @@ -0,0 +1,157 @@ +/* + * @flow strict + * Copyright (C) 2025 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import * as React from 'react'; + +import CDStubInfo from '../cdstub/CDStubInfo.js'; +import Layout from '../layout/index.js'; +import manifest from '../static/manifest.mjs'; +import CDTocLink from '../static/scripts/common/components/CDTocLink.js'; +import CDTocMediumListTable + from '../static/scripts/common/components/CDTocMediumListTable.js'; +import CDTocPossibleMediumListTable + from '../static/scripts/common/components/CDTocPossibleMediumListTable.js'; +import linkedEntities from '../static/scripts/common/linkedEntities.mjs'; +import FormRow from '../static/scripts/edit/components/FormRow.js'; +import FormRowText from '../static/scripts/edit/components/FormRowText.js'; +import FormSubmit from '../static/scripts/edit/components/FormSubmit.js'; + +component CDTocLookup( + cdStub?: CDStubT, + cdToc: CDTocT, + mediumCDTocs: $ReadOnlyArray, + possibleMediums: $ReadOnlyArray, + searchArtistForm: SearchFormT, + searchReleaseForm: SearchFormT, + tocString: StrOrNum, +) { + const title = l('Lookup CD'); + return ( + +

{title}

+ +

{l('Matching CDs')}

+ {mediumCDTocs.length > 0 ? ( + <> +

+ {l(`We found discs matching the information you requested, listed + below. If none of these are the release you are looking for, + you can search using the form below in order to attach this + disc to another MusicBrainz release, or to add a new one + if the search shows it is missing from the database.`)} +

+ +

+ {exp.l( + `We used disc ID {discid} + to look up this information.`, + {discid: }, + )} +

+ + ) : ( +

+ {l(`There are currently no discs in MusicBrainz associated with + the information you provided. You can search using the form + below in order to attach this disc to another MusicBrainz + release, or to add a new one if the search shows it is + missing from the database.`)} +

+ )} + + {cdStub ? ( + <> +

{l('CD stub found')}

+

+ {l(`A CD stub was found that matches the disc ID you provided. + If the below tracklist appears correct, you may use it as + a starting point for a new MusicBrainz release.`)} +

+

+ {texp.l( + '{artist} - {name}', + {artist: cdStub.artist, name: cdStub.title}, + )} +

+ +
+

+ +

+
+ {possibleMediums.length > 0 ? ( + <> +

{l('Possible mediums')}

+

+ {l(`Based on the above CD stub, we also found the following + releases in MusicBrainz that may be related:`)} +

+
+ + +

+ +

+ + + ) : null} + + ) : null} + +

{l('Search by artist')}

+
+ + + + + + + +

{l('Search by release')}

+
+ + + + + + + + {manifest( + 'common/components/CDTocMediumListTable', + {async: true}, + )} + {manifest( + 'common/components/CDTocPossibleMediumListTable', + {async: true}, + )} + {manifest( + 'common/components/ReleaseEvents', + {async: true}, + )} +
+ ); +} + +export default CDTocLookup; diff --git a/root/cdtoc/attach_artist_releases.tt b/root/cdtoc/attach_artist_releases.tt deleted file mode 100644 index dabda392b7a..00000000000 --- a/root/cdtoc/attach_artist_releases.tt +++ /dev/null @@ -1,56 +0,0 @@ -[%- PROCESS 'cdtoc/attach_list.tt' -%] - -[% WRAPPER 'layout.tt' title=lp('Attach CD TOC', 'header') full_width=1 %] -

[% lp('Attach CD TOC', 'header') %]

-

[% l('You are viewing releases by {artist}.', { artist => link_entity(artist) }) %]

- [% IF releases.size %] -

[% l('Please select the medium you wish to attach this CD TOC to.') %]

-
- - - [% WRAPPER 'components/with-pager.tt' %] - - - - - - - - - [%- IF c.try_get_session('tport') -%] - - [%- END -%] - - - - [% zebra = 0 %] - [% current_rg = '' %] - [% FOR release=releases %] - [%- IF release.release_group.gid != current_rg -%] - [% current_rg = release.release_group.gid %] - - [%- END -%] - [% zebra = zebra + 1 %] - [% attach_list_row(release) %] - [% END %] - -
[% l('Release') %][% l('Country') _ lp('/', 'and') _ l('Date') %][% l('Label') %][% l('Catalog#') %][% l('Barcode') %][% lp('Tagger', 'audio file metadata') %]
[% l('Release group: {release_group_link}', { release_group_link => link_entity(release.release_group) }) %]
- [% END %] -
- [% form_submit(lp('Attach CD TOC', 'interactive')) %] -
- [% attach_list_script() %] -
- [% ELSE %] -

[% ln('{artist} has no releases which have only {n} track.', - '{artist} has no releases which have {n} tracks.', - cdtoc.track_count, { artist => link_entity(artist), n => cdtoc.track_count }) %]

- [% END %] -

[% l("If you can't find what you're looking for, you can add a new release:") %]

-
- - - [% form_submit(l('Add a new release')) %] -
-[% END %] diff --git a/root/cdtoc/attach_list.tt b/root/cdtoc/attach_list.tt deleted file mode 100644 index 485c2648b09..00000000000 --- a/root/cdtoc/attach_list.tt +++ /dev/null @@ -1,98 +0,0 @@ -[% MACRO release_info(show_artists) BLOCK %] - [% IF show_artists %] - [% artist_credit(release.artist_credit) %] - [% END %] - - [% React.embed(c, 'static/scripts/common/components/ReleaseEvents', {events => React.to_json_array(release.events)}) %] - [% script_manifest('common/components/ReleaseEvents.js', {async => 'async'}) %] - - [% release_label_list(release.labels) %] - [% release_catno_list(release.labels) %] - [% release.barcode.format %] - [%- IF c.try_get_session('tport') -%] - [% tagger_icon(release) %] - [%- END -%] -[% END %] - -[% MACRO tracklist_toggle BLOCK %] - ([% l('show tracklist') %]) -[% END %] - -[% MACRO tracklist_block BLOCK %] - - - - - [% INCLUDE 'medium/tracklist.tt' tracks=medium.tracks %] -
- - -[% END %] - -[% MACRO attach_list_row(release, show_artists) BLOCK %] - - - [%~ link_entity(release) ~%] - - [% release_info(show_artists) %] - - [%- - attachable_mediums = []; - FOR medium=release.mediums; - IF medium.cdtoc_track_count == cdtoc.track_count; - attachable_mediums.push(medium); - END; - END; - -%] - [% FOR medium=attachable_mediums %] - [%- this_medium_has_cdtoc = medium_has_cdtoc.defined AND (medium.id == medium_has_cdtoc) -%] - - - - - [% tracklist_toggle %] - [% IF this_medium_has_cdtoc %] -
[% l('This CD TOC is already attached to this medium.') %]
- [% END %] - - - - [% tracklist_block %] - - [% END %] - [% IF was_mbid_search && !attachable_mediums.size %] - - - [% l('None of the mediums on this release can have the given CD TOC attached, because they have the wrong number of tracks.') %] - - - [% END %] -[% END %] - -[% MACRO attach_list_script BLOCK %] - -[% END %] diff --git a/root/cdtoc/index.tt b/root/cdtoc/index.tt deleted file mode 100644 index 772edbeb969..00000000000 --- a/root/cdtoc/index.tt +++ /dev/null @@ -1,11 +0,0 @@ -[%- WRAPPER 'layout.tt' title=l('Disc ID “{discid}”', { discid => cdtoc.discid }) full_width=1 -%] - -

[% l('Disc ID “{discid}”', { discid => link_cdtoc(cdtoc) }) %]

- - [%- cdtoc_json_obj = React.to_json_object(cdtoc) -%] - [% React.embed(c, 'cdtoc/CDTocInfo', {cdToc => cdtoc_json_obj}) %] - -

[% l('Attached to releases') %]

- [% INCLUDE 'cdtoc/list.tt' edit_links = 1 %] - -[%- END -%] diff --git a/root/cdtoc/list.tt b/root/cdtoc/list.tt deleted file mode 100644 index 6ffd9451559..00000000000 --- a/root/cdtoc/list.tt +++ /dev/null @@ -1,88 +0,0 @@ -[%- PROCESS 'cdtoc/attach_list.tt' -%] - - - - - - - - - - - - - [% IF c.session.tport %] - - [% END %] - [% IF edit_links AND c.user_exists %] - - [% END %] - - - - [%- FOR medium_cdtoc IN medium_cdtocs -%] - [% medium=medium_cdtoc.medium; - release=medium.release; - classes = []; - classes.push(loop.parity); - classes.push('mp') IF medium_cdtoc.edits_pending > 0; %] - - - - - - - - - - [% IF c.session.tport %] - - [% END %] - [% IF edit_links AND c.user_exists %] - - [% END %] - - [% tracklist_block %] - [%- END -%] - [% attach_list_script() %] - -
[% l('Position') %][% l('Title') %][% l('Artist') %][% l('Format') %][% l('Country') _ lp('/', 'and') _ l('Date') %][% l('Label') %][% l('Catalog#') %][% l('Barcode') %][% lp('Tagger', 'audio file metadata') %][% lp('Edit', 'verb, header') %]
- [% medium.position %]/[% release.medium_count %] - [% tracklist_toggle %] - [% link_entity(release) %][% artist_credit(release.artist_credit) %][% medium_format_name(medium) %] - [% React.embed(c, 'static/scripts/common/components/ReleaseEvents', {events => React.to_json_array(release.events)}) %] - [% script_manifest('common/components/ReleaseEvents.js', {async => 'async'}) %] - [% release_label_list(release.labels) %][% release_catno_list(release.labels) %][% release.barcode.format %] - [% tagger_icon(release) %] - - [%- IF !medium_cdtoc.is_perfect_match %] - - [% l('Set track lengths') %] - | - [%- END %] - - [% l('Remove') %] - | - - [% l('Move') %] - -
- -[% MACRO cdtoc_list_script BLOCK %] - -[% END %] diff --git a/root/cdtoc/lookup.tt b/root/cdtoc/lookup.tt deleted file mode 100644 index 05f39d1f849..00000000000 --- a/root/cdtoc/lookup.tt +++ /dev/null @@ -1,92 +0,0 @@ -[%- PROCESS 'cdtoc/attach_list.tt' -%] - -[% WRAPPER 'layout.tt' title=l('Lookup CD') full_width=1 %] -

[% l('Lookup CD') %]

-

[% l('Matching CDs') %]

- [% IF medium_cdtocs.size %] -

[% l('We found discs matching the information you requested, listed below. If none of these - are the release you are looking for, you can search using the form below - in order to attach this disc to another MusicBrainz release, or - to add a new one if the search shows it is missing from the database.') %]

- [% INCLUDE 'cdtoc/list.tt' edit_links=0 %] -

[% l('We used disc ID {discid} to look up this information.', { discid => link_cdtoc(cdtoc) }) %]

- [% ELSE %] -

[% l('There are currently no discs in MusicBrainz associated with the information - you provided. You can search using the form below in order to attach this disc - to another MusicBrainz release, or to add a new one if the search shows - it is missing from the database.') %]

- [% END %] - - [% IF cdstub %] -

[% l('CD stub found') %]

-

[% l('A CD stub was found that matches the disc ID you provided. If the below tracklist - appears correct, you may use it as a starting point for a new MusicBrainz release.') %]

-

[% l('{artist} - {name}', { artist => cdstub.artist, - name => cdstub.title }) %]

- [% INCLUDE 'cdstub/cdstub.tt' %] -
-

[% form_submit(lp('Import CD stub', 'interactive')) %]

-
- - [% IF possible_mediums.size %] -

[% l('Possible mediums') %]

-

[% l('Based on the above CD stub, we also found the following releases in MusicBrainz - that may be related:') %]

-
- - - - - - - - - - - - [%- IF c.try_get_session('tport') -%] - - [%- END -%] - - - [% FOR medium=possible_mediums %] - [% release=medium.release %] - - - - - [% release_info(1) %] - - [% tracklist_block %] - [% END %] - [% attach_list_script() %] - -
[% l('Release') %][% l('Medium') %][% l('Artist') %][% l('Country') _ lp('/', 'and') _ l('Date') %][% l('Label') %][% l('Catalog#') %][% l('Barcode') %][% lp('Tagger', 'audio file metadata') %]
[% link_entity(release) %] - [% medium.format_name %] [% medium.position %] - [% tracklist_toggle %] -
-

[% form_submit(l('Attach disc ID')) %]

-
- [% END %] - [% END %] - -

[% l('Search by artist') %]

-
- [% USE r = FormRenderer(query_artist) %] - - [% form_row_text(r, 'query', add_colon(l('Artist'))) %] -
- [% form_submit(l('Search')) %] -
-
- -

[% l('Search by release') %]

-
- [% USE r = FormRenderer(query_release) %] - - [% form_row_text(r, 'query', add_colon(l('Release title or MBID'))) %] -
- [% form_submit(l('Search')) %] -
-
-[% END %] diff --git a/root/medium/tracklist.tt b/root/medium/tracklist.tt deleted file mode 100644 index 14aad188419..00000000000 --- a/root/medium/tracklist.tt +++ /dev/null @@ -1,69 +0,0 @@ -[% # Converted to React at root/medium/MediumTracklist.js %] - -[% MACRO group_dd(group) BLOCK; - links = []; - links.push(link_entity(rel.target)) FOR rel=group.value; - links.join(', '); - END -%] - -[% data_tracks_started = 0 %] -[%- FOR track=tracks; recording = track.recording %] -[% IF track.is_data_track && !data_tracks_started %] - [% data_tracks_started = 1 %] - - - [% data_track_icon %] - [% l('Data tracks') %] - - -[% END %] -[%# model tracks as blank nodes %] - - - [% IF track.position == 0 %] - [% pregap_track_icon %] - [% END %] - [% track.position %] - [%- track.number -%] - - - [%~ link_entity(recording, 'show', track.name) ~%] - [% IF recording.relationships.size %] -
- - [% END %] - - [%- IF show_artists -%] - [% artist_credit(track.artist_credit) %] - [%- END -%] - [% format_length(track.length) %] - -[%- END -%] diff --git a/root/release/DiscIds.js b/root/release/DiscIds.js index b5993ae0a97..d23cb2fc855 100644 --- a/root/release/DiscIds.js +++ b/root/release/DiscIds.js @@ -9,8 +9,9 @@ import * as React from 'react'; -import {isPerfectMatch} from '../cdtoc/utils.js'; import {CatalystContext} from '../context.mjs'; +import CDTocEditColumn + from '../static/scripts/common/components/CDTocEditColumn.js'; import CDTocLink from '../static/scripts/common/components/CDTocLink.js'; import {groupBy} from '../static/scripts/common/utility/arrays.js'; @@ -47,29 +48,7 @@ component CDTocRow( {cdtoc.track_count} {formatTrackLength(cdtoc.length)} {showEditColumn ? ( - - {isPerfectMatch(medium, cdtoc) ? null : ( - <> - - {l('Set track lengths')} - - {' | '} - - )} - - {l('Remove')} - - {' | '} - - {l('Move')} - - + ) : null} ); diff --git a/root/server/components.mjs b/root/server/components.mjs index e05a64241c2..a59b1f92f22 100644 --- a/root/server/components.mjs +++ b/root/server/components.mjs @@ -98,7 +98,10 @@ export default { 'cdstub/DiscIdNotValid': (): Promise => import('../cdstub/DiscIdNotValid.js'), 'cdstub/ImportCDStub': (): Promise => import('../cdstub/ImportCDStub.js'), 'cdtoc/AttachCDTocConfirmation': (): Promise => import('../cdtoc/AttachCDTocConfirmation.js'), + 'cdtoc/AttachCDTocToArtistRelease': (): Promise => import('../cdtoc/AttachCDTocToArtistRelease.js'), 'cdtoc/AttachCDTocToRelease': (): Promise => import('../cdtoc/AttachCDTocToRelease.js'), + 'cdtoc/CDTocIndex': (): Promise => import('../cdtoc/CDTocIndex.js'), + 'cdtoc/CDTocLookup': (): Promise => import('../cdtoc/CDTocLookup.js'), 'cdtoc/RemoveDiscId': (): Promise => import('../cdtoc/RemoveDiscId.js'), 'cdtoc/SelectArtistForCDToc': (): Promise => import('../cdtoc/SelectArtistForCDToc.js'), 'cdtoc/SetTracklistDurations': (): Promise => import('../cdtoc/SetTracklistDurations.js'), diff --git a/root/static/scripts/common/components/CDTocArtistReleaseListTable.js b/root/static/scripts/common/components/CDTocArtistReleaseListTable.js new file mode 100644 index 00000000000..4f562c030ae --- /dev/null +++ b/root/static/scripts/common/components/CDTocArtistReleaseListTable.js @@ -0,0 +1,84 @@ +/* + * @flow strict + * Copyright (C) 2025 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import * as React from 'react'; + +import {SanitizedCatalystContext} from '../../../../context.mjs'; +import type {ReleaseWithMediumsAndReleaseGroupT} + from '../../relationship-editor/types.js'; + +import CDTocReleaseListRow from './CDTocReleaseListRow.js'; +import EntityLink from './EntityLink.js'; + +component CDTocArtistReleaseListTable( + cdTocTrackCount: number, + releases: $ReadOnlyArray, +) { + const $c = React.useContext(SanitizedCatalystContext); + const showTagger = Boolean($c?.session?.tport); + let currentReleaseGroup = ''; + let countInReleaseGroup = 0; + + return ( + + + + + + + + + + {showTagger ? : null} + + + + {releases.map((release, index) => { + const releaseGroup = release.releaseGroup; + const showSubHeader = + releaseGroup.gid !== currentReleaseGroup; + currentReleaseGroup = releaseGroup.gid; + countInReleaseGroup = showSubHeader + ? 0 + : countInReleaseGroup + 1; + + return ( + + {showSubHeader ? ( + + + + ) : null} + + + ); + })} + +
{l('Release')}{l('Artist')}{l('Country') + lp('/', 'and') + l('Date')}{l('Label')}{l('Catalog#')}{l('Barcode')}{lp('Tagger', 'audio file metadata')}
+ {exp.l( + 'Release group: {release_group_link}', + { + release_group_link: ( + + ), + }, + )} +
+ ); +} + +export default (hydrate>( + 'div.cd-toc-release-list-table-container', + CDTocArtistReleaseListTable, +): component(...React.PropsOf)); diff --git a/root/static/scripts/common/components/CDTocEditColumn.js b/root/static/scripts/common/components/CDTocEditColumn.js new file mode 100644 index 00000000000..a5cd9563cef --- /dev/null +++ b/root/static/scripts/common/components/CDTocEditColumn.js @@ -0,0 +1,46 @@ +/* + * @flow strict + * Copyright (C) 2022 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import {isPerfectMatch} from '../../../../cdtoc/utils.js'; + +component CDTocEditColumn( + mediumCDToc: MediumCDTocT, +) { + const cdToc = mediumCDToc.cdtoc; + const medium = mediumCDToc.medium; + invariant(medium, 'No medium found'); + + return ( + + {isPerfectMatch(medium, cdToc) ? null : ( + <> + + {l('Set track lengths')} + + {' | '} + + )} + + {l('Remove')} + + {' | '} + + {l('Move')} + + + ); +} + +export default CDTocEditColumn; diff --git a/root/static/scripts/common/components/CDTocMediumListRow.js b/root/static/scripts/common/components/CDTocMediumListRow.js new file mode 100644 index 00000000000..314fa2f7597 --- /dev/null +++ b/root/static/scripts/common/components/CDTocMediumListRow.js @@ -0,0 +1,95 @@ +/* + * @flow strict + * Copyright (C) 2025 MetaBrainz Foundation + * + * This file is part of MusicBrainz, the open internet music database, + * and is licensed under the GPL version 2, or (at your option) any + * later version: http://www.gnu.org/licenses/gpl-2.0.txt + */ + +import * as React from 'react'; + +import ReleaseCatnoList from '../../../../components/ReleaseCatnoList.js'; +import ReleaseLabelList from '../../../../components/ReleaseLabelList.js'; +import loopParity from '../../../../utility/loopParity.js'; +import formatBarcode from '../utility/formatBarcode.js'; +import mediumFormatName from '../utility/mediumFormatName.js'; + +import ArtistCreditLink from './ArtistCreditLink.js'; +import CDTocEditColumn from './CDTocEditColumn.js'; +import {CDTocTracklistBlock, CDTocTracklistToggle} from './CDTocTracklist.js'; +import EntityLink from './EntityLink.js'; +import ReleaseEvents from './ReleaseEvents.js'; +import TaggerIcon from './TaggerIcon.js'; + +component CDTocMediumListRow( + index: number, + mediumCDToc: MediumCDTocT, + releaseMap: {[releaseId: number]: ReleaseT}, + showEditColumn: boolean = false, + showTagger: boolean = false, +) { + const loopClass = loopParity(index); + const editsPendingClass = mediumCDToc.editsPending ? ' mp' : ''; + const medium = mediumCDToc.medium; + invariant(medium, 'No medium found'); + const release = releaseMap[medium.release_id]; + const releaseMediumCount = release.mediums?.length; + invariant(releaseMediumCount != null, 'No medium count found'); + const hasLoadedTracks = Boolean(medium.tracks); + + const [hidden, setHidden] = React.useState(true); + + function onButtonClick(event: SyntheticMouseEvent) { + event.preventDefault(); + setHidden(!hidden); + } + + return ( + <> + + + {medium.position + '/' + releaseMediumCount} + {hasLoadedTracks ? ( +