Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e76bb8a
Drop unneeded edit_form in event Role::Create
reosarevok Sep 11, 2024
6ea6415
Remove activeUser param from AC editor
reosarevok Sep 16, 2024
f57b56d
Drop seemingly useless class
reosarevok Apr 24, 2025
32663a7
MBS-14180: Drop classical featuring guess feature
reosarevok Oct 17, 2025
220a844
Update guessFeat to use Flow and add non-ko version
reosarevok Oct 17, 2025
db18254
MBS-12761: Convert recording edit form to React
reosarevok Sep 11, 2024
2921feb
Use useEffect instead of manifest to load form tools
reosarevok Aug 14, 2025
0aeec6d
Move formUnload code to hook
reosarevok Aug 14, 2025
debb05f
Use React validation for name and AC
reosarevok Aug 20, 2025
09e8009
Use form artist credit for ArtistCreditEditor
reosarevok Aug 21, 2025
184aec5
Check for valid edit notes on RecordingEditForm
reosarevok Aug 21, 2025
89bcd52
Ignore invalid artist IDs in `createInitialNamesState`
mwiencek Sep 2, 2025
3eb6b80
Move unformatTrackLength to its own file
reosarevok Sep 26, 2025
d78387b
Make unformatTrackLength work consistently with Perl
reosarevok Sep 30, 2025
75451e1
Check for valid recording length on RecordingEditForm
reosarevok Oct 8, 2025
aba0008
MBS-12761: Move ISRC validation and changing to React state
reosarevok Oct 8, 2025
1663f11
Also validate edit note on initial state
reosarevok Oct 10, 2025
d72bf2d
Show external links bubble on focus
reosarevok Oct 16, 2025
5d85f4a
Use the three-argument form of `useReducer`
reosarevok Oct 16, 2025
118894a
Ensure no accidental writes on nameStateCtx
reosarevok Oct 16, 2025
0d6b18a
Use useLayoutEffect for Bubble
reosarevok Oct 16, 2025
e2ded36
Also validate length on initial state
reosarevok Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ export default [
'root/static/scripts/edit/components/UrlRelationshipCreditFieldset.js',
'root/static/scripts/edit/externalLinks.js',
'root/static/scripts/event/components/EventEditForm.js',
'root/static/scripts/recording/components/RecordingEditForm.js',
'root/static/scripts/relationship-editor/components/DialogPreview.js',
],
rules: {
Expand Down
1 change: 0 additions & 1 deletion lib/MusicBrainz/Server/Controller/Event.pm
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ with 'MusicBrainz::Server::Controller::Role::Merge' => {
with 'MusicBrainz::Server::Controller::Role::Create' => {
form => 'Event',
edit_type => $EDIT_EVENT_CREATE,
dialog_template => 'event/edit_form.tt',
};

with 'MusicBrainz::Server::Controller::Role::Edit' => {
Expand Down
1 change: 0 additions & 1 deletion lib/MusicBrainz/Server/Controller/Recording.pm
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ with 'MusicBrainz::Server::Controller::Role::Create' => {
$ret{form_args} = { used_by_tracks => 0 };
return %ret;
},
dialog_template => 'recording/edit_form.tt',
};

sub _merge_load_entities {
Expand Down
12 changes: 9 additions & 3 deletions lib/MusicBrainz/Server/Controller/Role/Create.pm
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ role {
my $type = model_to_type($model);
my $entity;
my %props;
my %edit_arguments = $params->edit_arguments->($self, $c);

if ($model eq 'Event' || $model eq 'Genre') {
my $form = $c->form( form => $params->form );
if ($model eq 'Event' || $model eq 'Genre' || $model eq 'Recording') {
my $type = model_to_type($model);
my %form_args = %{ $edit_arguments{form_args} || {}};
my $form = $c->form( form => $params->form, ctx => $c, %form_args );
%props = ( form => $form->TO_JSON );

$c->stash(
Expand Down Expand Up @@ -111,14 +114,17 @@ role {
if ($self->does('MusicBrainz::Server::Controller::Role::IdentifierSet')) {
$self->munge_compound_text_fields($c, $form);
}
if ($model eq 'Recording') {
$props{usedByTracks} = $form->used_by_tracks;
}
},
redirect => sub {
$c->response->redirect($c->uri_for_action(
$self->action_for('show'), [ $entity->gid ]));
},
no_redirect => $args{within_dialog},
edit_rels => 1,
$params->edit_arguments->($self, $c),
%edit_arguments,
);

if ($ENTITIES{$type}{artist_credits}) {
Expand Down
13 changes: 11 additions & 2 deletions lib/MusicBrainz/Server/Controller/Role/Edit.pm
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,23 @@ role {
method 'edit' => sub {
my ($self, $c) = @_;

my @react_models = qw( Event Genre);
my @react_models = qw( Event Genre Recording );
my $entity_name = $self->{entity_name};
my $edit_entity = $c->stash->{ $entity_name };
my $model = $self->{model};
my $type = model_to_type($model);
my %props;
my %edit_arguments = $params->edit_arguments->($self, $c, $edit_entity);

if (any { $_ eq $model } @react_models) {
my $type = model_to_type($model);

my %form_args = %{ $edit_arguments{form_args} || {}};
my $form = $c->form(
form => $params->form,
ctx => $c,
init_object => $edit_entity,
%form_args,
);

%props = (
Expand Down Expand Up @@ -82,12 +88,15 @@ role {
$self->munge_compound_text_fields($c, $form);
$self->stash_current_identifier_values($c, $edit_entity->id);
}
if ($model eq 'Recording') {
$props{usedByTracks} = $form->used_by_tracks;
}
},
redirect => sub {
$c->response->redirect(
$c->uri_for_action($self->action_for('show'), [ $edit_entity->gid ]));
},
$params->edit_arguments->($self, $c, $edit_entity),
%edit_arguments,
);

if ($ENTITIES{$type}{artist_credits}) {
Expand Down
21 changes: 18 additions & 3 deletions lib/MusicBrainz/Server/Track.pm
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,40 @@ sub FormatTrackLength
sprintf($print_formats->{ms}, $minutes, $seconds);
}

# Keep in sync with static/scripts/common/utility/unformatTrackLength.js
sub UnformatTrackLength
{
my $length = shift;

if ($length =~ /^\s*(\d{1,3}):(\d{1,2}):(\d{1,2})\s*$/ && $2 < 60 && $3 < 60)
# ?:?? or just space are allowed to indicate unknown/empty
if ($length =~ /^\s*\?:\?\?\s*$/ || $length =~ /^\s*$/)
{
return undef;
}
# Check for HH:MM:SS
elsif ($length =~ /^\s*(\d{1,3}):(\d{1,2}):(\d{1,2})\s*$/ && $2 < 60 && $3 < 60)
{
return ($1 * 3600 + $2 * 60 + $3) * 1000;
}
# Check for MM:SS
elsif ($length =~ /^\s*(\d+):(\d{1,2})\s*$/ && $2 < 60)
{
return ($1 * 60 + $2) * 1000;
}
# Check for :SS
elsif ($length =~ /^\s*:(\d{1,2})\s*$/ && $1 < 60)
{
return ($1) * 1000;
}
# Check for XX ms
elsif ($length =~ /^\s*(\d+(\.\d+)?)?\s+ms\s*$/)
{
return int($1);
}
elsif ($length =~ /^\s*\?:\?\?\s*$/ || $length =~ /^\s*$/)
# Check for just a number of seconds
elsif ($length =~ /^\s*(\d+)\s*$/)
{
return undef;
return ($1) * 1000;
}
else {
confess("$length is not a valid track length");
Expand Down
30 changes: 30 additions & 0 deletions root/recording/CreateRecording.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* @flow strict-local
* Copyright (C) 2024 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 RecordingEditForm
from '../static/scripts/recording/components/RecordingEditForm.js';

import type {RecordingFormT} from './types.js';

component CreateRecording(form: RecordingFormT, usedByTracks: boolean) {
return (
<Layout fullWidth title={lp('Add recording', 'header')}>
<div id="content">
<h1>{lp('Add recording', 'header')}</h1>
<RecordingEditForm form={form} usedByTracks={usedByTracks} />
</div>
{manifest('recording/components/RecordingEditForm', {async: true})}
{manifest('relationship-editor', {async: true})}
</Layout>
);
}

export default CreateRecording;
36 changes: 36 additions & 0 deletions root/recording/EditRecording.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* @flow strict-local
* Copyright (C) 2024 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 manifest from '../static/manifest.mjs';
import RecordingEditForm
from '../static/scripts/recording/components/RecordingEditForm.js';

import RecordingLayout from './RecordingLayout.js';
import type {RecordingFormT} from './types.js';

component EditRecording(
entity: RecordingT,
form: RecordingFormT,
usedByTracks: boolean,
) {
return (
<RecordingLayout
entity={entity}
fullWidth
page="edit"
title={lp('Edit recording', 'header')}
>
<RecordingEditForm form={form} usedByTracks={usedByTracks} />
{manifest('recording/components/RecordingEditForm', {async: true})}
{manifest('relationship-editor', {async: true})}
</RecordingLayout>
);
}

export default EditRecording;
6 changes: 0 additions & 6 deletions root/recording/create.tt

This file was deleted.

129 changes: 0 additions & 129 deletions root/recording/edit_form.tt

This file was deleted.

19 changes: 19 additions & 0 deletions root/recording/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* @flow strict
* Copyright (C) 2024 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
*/

export type RecordingFormT = FormT<{
+artist_credit: ArtistCreditFieldT,
+comment: FieldT<string>,
+edit_note: FieldT<string>,
+isrcs: TextListFieldT,
+length: FieldT<string>,
+make_votable: FieldT<boolean>,
+name: FieldT<string | null>,
+video: FieldT<boolean>,
}>;
5 changes: 3 additions & 2 deletions root/server/components.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ export default {
'place/PlaceMap': (): Promise<mixed> => import('../place/PlaceMap.js'),
'place/PlaceMerge': (): Promise<mixed> => import('../place/PlaceMerge.js'),
'place/PlacePerformances': (): Promise<mixed> => import('../place/PlacePerformances.js'),
'recording/CreateRecording': (): Promise<mixed> => import('../recording/CreateRecording.js'),
'recording/DeleteRecording': (): Promise<mixed> => import('../recording/DeleteRecording.js'),
'recording/EditRecording': (): Promise<mixed> => import('../recording/EditRecording.js'),
'recording/RecordingFingerprints': (): Promise<mixed> => import('../recording/RecordingFingerprints.js'),
'recording/RecordingIndex': (): Promise<mixed> => import('../recording/RecordingIndex.js'),
'recording/RecordingMerge': (): Promise<mixed> => import('../recording/RecordingMerge.js'),
Expand Down Expand Up @@ -546,12 +548,11 @@ export default {
'static/scripts/common/components/WarningIcon': (): Promise<mixed> => import('../static/scripts/common/components/WarningIcon.js'),
'static/scripts/edit/components/AddIcon': (): Promise<mixed> => import('../static/scripts/edit/components/AddIcon.js'),
'static/scripts/edit/components/FormRowNameWithGuessCase': (): Promise<mixed> => import('../static/scripts/edit/components/FormRowNameWithGuessCase.js'),
'static/scripts/edit/components/FormRowTextList': (): Promise<mixed> => import('../static/scripts/edit/components/FormRowTextList.js'),
'static/scripts/edit/components/FormRowTextListSimple': (): Promise<mixed> => import('../static/scripts/edit/components/FormRowTextListSimple.js'),
'static/scripts/edit/components/GuessCaseIcon': (): Promise<mixed> => import('../static/scripts/edit/components/GuessCaseIcon.js'),
'static/scripts/edit/components/HydratedDateRangeFieldset': (): Promise<mixed> => import('../static/scripts/edit/components/HydratedDateRangeFieldset.js'),
'static/scripts/edit/components/InformationIcon': (): Promise<mixed> => import('../static/scripts/edit/components/InformationIcon.js'),
'static/scripts/recording/RecordingName': (): Promise<mixed> => import('../static/scripts/recording/RecordingName.js'),
'static/scripts/edit/components/StandaloneFormRowTextList': (): Promise<mixed> => import('../static/scripts/edit/components/StandaloneFormRowTextList.js'),
'static/scripts/relationship-editor/components/RelationshipEditorWrapper': (): Promise<mixed> => import('../static/scripts/relationship-editor/components/RelationshipEditorWrapper.js'),
'static/scripts/release-editor/components/EditNoteTab': (): Promise<mixed> => import('../static/scripts/release-editor/components/EditNoteTab.js'),
'static/scripts/series/components/SeriesRelationshipEditor': (): Promise<mixed> => import('../static/scripts/series/components/SeriesRelationshipEditor.js'),
Expand Down
Loading