From ab52b84ca0f0868c21198693d912af28fe863435 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Thu, 16 May 2024 01:11:32 -0400
Subject: [PATCH 01/18] Convert form_row_text_list to React
---
root/components/forms.tt | 3 +-
.../scripts/edit/components/AddButton.js | 26 ++++
.../edit/components/FormRowTextList.js | 141 ++++++++++++++++++
3 files changed, 169 insertions(+), 1 deletion(-)
create mode 100644 root/static/scripts/edit/components/AddButton.js
create mode 100644 root/static/scripts/edit/components/FormRowTextList.js
diff --git a/root/components/forms.tt b/root/components/forms.tt
index 9fb99031880..e39b1383b10 100644
--- a/root/components/forms.tt
+++ b/root/components/forms.tt
@@ -133,7 +133,8 @@
[% END %]
[%- END -%]
-[%- MACRO form_row_text_list(r, field_name, label, item_name) BLOCK -%]
+[%- MACRO form_row_text_list(r, field_name, label, item_name) BLOCK # Converted to React at root/static/scripts/edit/components/FormTowTextList.js
+-%]
[% WRAPPER form_row %]
diff --git a/root/static/scripts/edit/components/AddButton.js b/root/static/scripts/edit/components/AddButton.js
new file mode 100644
index 00000000000..1e073626672
--- /dev/null
+++ b/root/static/scripts/edit/components/AddButton.js
@@ -0,0 +1,26 @@
+/*
+ * @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
+ */
+
+component AddButton(
+ onClick: (event: SyntheticEvent
) => void,
+ label?: string,
+) {
+ if (label == null) {
+ return ;
+ }
+
+ return (
+
+ );
+};
+
+export default AddButton;
+
\ No newline at end of file
diff --git a/root/static/scripts/edit/components/FormRowTextList.js b/root/static/scripts/edit/components/FormRowTextList.js
new file mode 100644
index 00000000000..bdd1cb53699
--- /dev/null
+++ b/root/static/scripts/edit/components/FormRowTextList.js
@@ -0,0 +1,141 @@
+/*
+ * @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
+ */
+
+import React, {useState} from 'react';
+
+import AddButton from './AddButton.js';
+import FieldErrors from './FieldErrors.js';
+import FormLabel from './FormLabel.js';
+import FormRow from './FormRow.js';
+import RemoveButton from './RemoveButton.js';
+
+type TextListRowProps = {
+ +name: string,
+ +value?: string,
+ +template?: boolean,
+ +onChange?: (event: SyntheticEvent) => void,
+ +onRemove?: (event: SyntheticEvent) => void,
+ +index?: number,
+};
+
+component TextListRow(...{
+ name,
+ value = "",
+ template = false,
+ onChange = () => {},
+ onRemove = () => {},
+ index = 0
+}: TextListRowProps) {
+ if (template) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+const initialRows = (repeatable: RepeatableFieldT>) => {
+ if (repeatable.field.length === 0) {
+ return [{name: repeatable.html_name + '.0', value: ''}];
+ }
+
+ return repeatable.field.map((field, index) => ({
+ name: repeatable.html_name + '.' + index,
+ value: field.value ?? '',
+ }));
+};
+
+component FormRowTextList(
+ repeatable: RepeatableFieldT>,
+ label: string,
+ itemName: string,
+ required: boolean = false,
+) {
+ const newRow = (name: string, value: string, index: number) => {
+ return {name: name + '.' + index, value};
+ };
+
+ const [rows, setRows] = useState(initialRows(repeatable));
+
+ const add = () => {
+ const index = rows.length;
+
+ setRows([...rows, newRow(repeatable.html_name, '', index)]);
+ };
+
+ const change = (index: number, value: string) => {
+ const newRows = [...rows];
+ newRows[index] = newRow(repeatable.html_name, value, index);
+ setRows(newRows);
+ };
+
+ const removeRow = (index: number) => {
+ if (rows.length === 1) {
+ setRows([newRow(repeatable.html_name, '', 0)]);
+ return;
+ }
+
+ setRows(rows.filter((_, i) => i !== index));
+ };
+
+ return (
+
+
+
+
+
{}}
+ onRemove={() => {}}
+ template
+ />
+
+ {rows.map((field, index) => (
+ change(index, event.currentTarget.value)}
+ onRemove={() => removeRow(index)}
+ value={field.value}
+ />
+ ))}
+
+
+
+
+
+
+ );
+}
+
+export default FormRowTextList;
From 99825eda3ff6cafaa1d66cb03af5d8eb1fb63834 Mon Sep 17 00:00:00 2001
From: Serial <69764315+Serial-ATA@users.noreply.github.com>
Date: Thu, 16 May 2024 02:04:06 -0400
Subject: [PATCH 02/18] Fix ESLint warnings
---
root/static/scripts/edit/components/AddButton.js | 11 +++++------
.../scripts/edit/components/FormRowTextList.js | 14 ++++++--------
2 files changed, 11 insertions(+), 14 deletions(-)
diff --git a/root/static/scripts/edit/components/AddButton.js b/root/static/scripts/edit/components/AddButton.js
index 1e073626672..cdef659bdee 100644
--- a/root/static/scripts/edit/components/AddButton.js
+++ b/root/static/scripts/edit/components/AddButton.js
@@ -12,15 +12,14 @@ component AddButton(
label?: string,
) {
if (label == null) {
- return ;
+ return ;
}
-
+
return (
-
);
}
@@ -74,9 +66,10 @@ const initialRows = (repeatable: RepeatableFieldT>) => {
};
component FormRowTextList(
- repeatable: RepeatableFieldT>,
+ addButtonLabel: string,
label: string,
- itemName: string,
+ removeButtonLabel: string,
+ repeatable: RepeatableFieldT>,
required: boolean = false,
) {
const newRow = (name: string, value: string, index: number) => {
@@ -113,6 +106,7 @@ component FormRowTextList(
@@ -122,12 +116,13 @@ component FormRowTextList(
name={field.name}
onChange={(event) => change(index, event.currentTarget.value)}
onRemove={() => removeRow(index)}
+ removeButtonLabel={removeButtonLabel}
value={field.value}
/>
))}
From 87e3dde7d4d59d82a09c85179a319ab78f143e1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicol=C3=A1s=20Tamargo?=
Date: Fri, 6 Sep 2024 10:38:53 +0200
Subject: [PATCH 06/18] Remove unneeded template hack
This was a hack needed for JQuery to work on this, but with
React it's AFAICT entirely useless.
---
.../edit/components/FormRowTextList.js | 30 ++-----------------
1 file changed, 3 insertions(+), 27 deletions(-)
diff --git a/root/static/scripts/edit/components/FormRowTextList.js b/root/static/scripts/edit/components/FormRowTextList.js
index fca66d7ce27..aa3d4e6e251 100644
--- a/root/static/scripts/edit/components/FormRowTextList.js
+++ b/root/static/scripts/edit/components/FormRowTextList.js
@@ -16,30 +16,12 @@ import FormRow from './FormRow.js';
import RemoveButton from './RemoveButton.js';
component TextListRow(
- index: number = 0,
name: string,
- onChange: (event: SyntheticEvent) => void = () => {},
- onRemove: (event: SyntheticEvent) => void = () => {},
+ onChange: (event: SyntheticEvent) => void,
+ onRemove: (event: SyntheticEvent) => void,
removeButtonLabel: string,
- template: boolean = false,
- value: string = '',
+ value: string,
) {
- if (template) {
- return (
-
-
-
-
- );
- }
-
return (
-
-
{rows.map((field, index) => (
Date: Fri, 6 Sep 2024 11:06:43 +0200
Subject: [PATCH 07/18] Standardize state function names
---
root/static/scripts/edit/components/FormRowTextList.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/root/static/scripts/edit/components/FormRowTextList.js b/root/static/scripts/edit/components/FormRowTextList.js
index aa3d4e6e251..71af505bf11 100644
--- a/root/static/scripts/edit/components/FormRowTextList.js
+++ b/root/static/scripts/edit/components/FormRowTextList.js
@@ -60,13 +60,13 @@ component FormRowTextList(
const [rows, setRows] = useState(initialRows(repeatable));
- const add = () => {
+ const addRow = () => {
const index = rows.length;
setRows([...rows, newRow(repeatable.html_name, '', index)]);
};
- const change = (index: number, value: string) => {
+ const changeRow = (index: number, value: string) => {
const newRows = [...rows];
newRows[index] = newRow(repeatable.html_name, value, index);
setRows(newRows);
@@ -90,7 +90,7 @@ component FormRowTextList(
change(index, event.currentTarget.value)}
+ onChange={(event) => changeRow(index, event.currentTarget.value)}
onRemove={() => removeRow(index)}
removeButtonLabel={removeButtonLabel}
value={field.value}
@@ -98,7 +98,7 @@ component FormRowTextList(
))}
From d53c556f15804b5dfe02e58ef1d9b086e538d3d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicol=C3=A1s=20Tamargo?=
Date: Fri, 6 Sep 2024 11:09:41 +0200
Subject: [PATCH 08/18] Use splice for RemoveRow
This matches what we do elsewhere in the codebase.
---
root/static/scripts/edit/components/FormRowTextList.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/root/static/scripts/edit/components/FormRowTextList.js b/root/static/scripts/edit/components/FormRowTextList.js
index 71af505bf11..f5c41ebe170 100644
--- a/root/static/scripts/edit/components/FormRowTextList.js
+++ b/root/static/scripts/edit/components/FormRowTextList.js
@@ -78,7 +78,9 @@ component FormRowTextList(
return;
}
- setRows(rows.filter((_, i) => i !== index));
+ const newRows = [...rows];
+ newRows.splice(index, 1);
+ setRows(newRows);
};
return (
From 649270608ae5b40e65a3b0710c79c9324f77bd87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicol=C3=A1s=20Tamargo?=
Date: Fri, 6 Sep 2024 11:43:11 +0200
Subject: [PATCH 09/18] Reuse AddButton in FormRowSelectList
It makes sense for this to be a reusable component, but then
we should reuse it.
---
root/area/edit_form.tt | 3 +++
.../scripts/edit/components/AddButton.js | 19 ++++++++++++++++---
.../edit/components/FormRowSelectList.js | 10 ++++------
.../edit/components/FormRowTextList.js | 7 ++++++-
4 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/root/area/edit_form.tt b/root/area/edit_form.tt
index 06d170794ce..c5d4b6e66c0 100644
--- a/root/area/edit_form.tt
+++ b/root/area/edit_form.tt
@@ -12,18 +12,21 @@
[% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
repeatable => form_to_json(form.field('iso_3166_1')),
label => 'ISO 3166-1:',
+ addButtonId => 'add-iso-3166-1',
addButtonLabel => 'Add ISO 3166-1',
removeButtonLabel => 'Remove ISO 3166-1',
}) %]
[% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
repeatable => form_to_json(form.field('iso_3166_2')),
label => 'ISO 3166-2:',
+ addButtonId => 'add-iso-3166-2',
addButtonLabel => 'Add ISO 3166-2',
removeButtonLabel => 'Remove ISO 3166-2',
}) %]
[% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
repeatable => form_to_json(form.field('iso_3166_3')),
label => 'ISO 3166-3:',
+ addButtonId => 'add-iso-3166-3',
addButtonLabel => 'Add ISO 3166-3',
removeButtonLabel => 'Remove ISO 3166-3',
}) %]
diff --git a/root/static/scripts/edit/components/AddButton.js b/root/static/scripts/edit/components/AddButton.js
index 8d27b2870c9..7b0004455cf 100644
--- a/root/static/scripts/edit/components/AddButton.js
+++ b/root/static/scripts/edit/components/AddButton.js
@@ -8,15 +8,28 @@
*/
component AddButton(
- onClick: (event: SyntheticEvent) => void,
+ id: string,
+ onClick: (event: SyntheticEvent) => void,
label?: string,
) {
if (label == null) {
- return ;
+ return (
+
+ );
}
return (
-
+
{label}
);
diff --git a/root/static/scripts/edit/components/FormRowSelectList.js b/root/static/scripts/edit/components/FormRowSelectList.js
index 0a2a5f98bf8..19b4b27307f 100644
--- a/root/static/scripts/edit/components/FormRowSelectList.js
+++ b/root/static/scripts/edit/components/FormRowSelectList.js
@@ -9,6 +9,7 @@
import SelectField from '../../common/components/SelectField.js';
+import AddButton from './AddButton.js';
import FieldErrors from './FieldErrors.js';
import FormRow from './FormRow.js';
@@ -49,14 +50,11 @@ component FormRowSelectList(
))}
{hideAddButton ? null : (
-
- {addLabel}
-
+ />
)}
diff --git a/root/static/scripts/edit/components/FormRowTextList.js b/root/static/scripts/edit/components/FormRowTextList.js
index f5c41ebe170..11f0b8fb373 100644
--- a/root/static/scripts/edit/components/FormRowTextList.js
+++ b/root/static/scripts/edit/components/FormRowTextList.js
@@ -49,6 +49,7 @@ const initialRows = (repeatable: RepeatableFieldT>) => {
component FormRowTextList(
addButtonLabel: string,
+ addButtonId: string,
label: string,
removeButtonLabel: string,
repeatable: RepeatableFieldT>,
@@ -100,7 +101,11 @@ component FormRowTextList(
))}
From d4c4dd8a29d728af51ca4c37a5dc8d5cc948950f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicol=C3=A1s=20Tamargo?=
Date: Fri, 6 Sep 2024 12:26:40 +0200
Subject: [PATCH 10/18] Use FormRowTextList everywhere
This allows us to remove the TextList jquery code that the
TT version uses.
---
root/artist/edit_form.tt | 16 ++++++-
root/components/forms.tt | 34 --------------
root/label/edit_form.tt | 16 ++++++-
root/recording/edit_form.tt | 8 +++-
root/static/scripts/artist/edit.js | 1 +
root/static/scripts/edit.js | 1 -
root/static/scripts/edit/MB/TextList.js | 61 -------------------------
root/static/scripts/label/edit.js | 2 +
root/static/scripts/recording/edit.js | 1 +
root/static/scripts/work/edit.js | 2 +
root/work/edit_form.tt | 8 +++-
11 files changed, 48 insertions(+), 102 deletions(-)
delete mode 100644 root/static/scripts/edit/MB/TextList.js
diff --git a/root/artist/edit_form.tt b/root/artist/edit_form.tt
index 3506b6874a8..dabe29f0c35 100644
--- a/root/artist/edit_form.tt
+++ b/root/artist/edit_form.tt
@@ -26,8 +26,20 @@
[% field_errors(r.form, 'area.name') %]
[% END %]
- [%- form_row_text_list(r, 'ipi_codes', add_colon(l('IPI codes')), l('IPI code')) -%]
- [%- form_row_text_list(r, 'isni_codes', add_colon(l('ISNI codes')), l('ISNI code')) -%]
+ [% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
+ repeatable => form_to_json(form.field('ipi_codes')),
+ label => add_colon(l('IPI codes')),
+ addButtonId => 'add-ipi-code',
+ addButtonLabel => lp('Add IPI code', 'interactive'),
+ removeButtonLabel => lp('Remove IPI code', 'interactive'),
+ }) %]
+ [% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
+ repeatable => form_to_json(form.field('isni_codes')),
+ label => add_colon(l('ISNI codes')),
+ addButtonId => 'add-isni-code',
+ addButtonLabel => lp('Add ISNI code', 'interactive'),
+ removeButtonLabel => lp('Remove ISNI code', 'interactive'),
+ }) %]
[% date_range_fieldset(r, 'label', l('This label has ended.')) %]
diff --git a/root/recording/edit_form.tt b/root/recording/edit_form.tt
index a703f5da200..d637a73a042 100644
--- a/root/recording/edit_form.tt
+++ b/root/recording/edit_form.tt
@@ -42,7 +42,13 @@
[%- END -%]
[%- END -%]
[%- form_row_checkbox(r, 'video', l('Video')) -%]
- [%- form_row_text_list(r, 'isrcs', add_colon(l('ISRCs')), l('ISRC')) -%]
+ [% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
+ repeatable => form_to_json(form.field('isrcs')),
+ label => add_colon(l('ISRCs')),
+ addButtonId => 'add-isrc',
+ addButtonLabel => lp('Add ISRC', 'interactive'),
+ removeButtonLabel => lp('Remove ISRC', 'interactive'),
+ }) %]
[% PROCESS 'forms/relationship-editor.tt' %]
diff --git a/root/static/scripts/artist/edit.js b/root/static/scripts/artist/edit.js
index 6ccf36351f8..fc67cfbad08 100644
--- a/root/static/scripts/artist/edit.js
+++ b/root/static/scripts/artist/edit.js
@@ -8,6 +8,7 @@
*/
import './components/ArtistCreditRenamer.js';
+import '../edit/components/FormRowTextList.js';
import '../relationship-editor/components/RelationshipEditorWrapper.js';
import typeBubble from '../edit/typeBubble.js';
diff --git a/root/static/scripts/edit.js b/root/static/scripts/edit.js
index ee090169761..59d17c8b5f8 100644
--- a/root/static/scripts/edit.js
+++ b/root/static/scripts/edit.js
@@ -21,7 +21,6 @@ require('./edit/MB/Control/Bubble.js');
require('./edit/URLCleanup.js');
require('./edit/MB/edit.js');
require('./edit/MB/reltypeslist.js');
-require('./edit/MB/TextList.js');
require('./edit/check-duplicates.js');
require('./guess-case/MB/Control/GuessCase.js');
diff --git a/root/static/scripts/edit/MB/TextList.js b/root/static/scripts/edit/MB/TextList.js
deleted file mode 100644
index 79b6e0e0bac..00000000000
--- a/root/static/scripts/edit/MB/TextList.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2012 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 $ from 'jquery';
-
-import MB from '../../common/MB.js';
-
-MB.Form = (MB.Form) ? MB.Form : {};
-
-MB.Form.TextList = function (input) {
- var template = input + '-template';
- var self = {};
- var $template = $('.' + template.replace(/\./g, '\\.'));
- var counter = 0;
-
- self.removeEvent = function () {
- $(this).closest('div.text-list-row').remove();
- };
-
- self.init = function (maxIndex) {
- counter = maxIndex;
- $template
- .parent()
- .find('div.text-list-row input.value')
- .siblings('button.remove-item')
- .bind('click.mb', self.removeEvent);
-
- return self;
- };
-
- self.add = function (initValue) {
- $template.clone()
- .removeClass(template)
- .insertAfter($template
- .parent()
- .find('div.text-list-row')
- .last())
- .show()
- .find('input.value')
- .attr('name', input + '.' + counter)
- .val(initValue)
- .end()
- .find('button.remove-item')
- .bind('click.mb', self.removeEvent);
-
- counter++;
-
- return self;
- };
-
- $template.parent().find('button.add-item').bind('click.mb', function () {
- self.add('');
- });
-
- return self;
-};
diff --git a/root/static/scripts/label/edit.js b/root/static/scripts/label/edit.js
index bc875ef2aae..e793813c057 100644
--- a/root/static/scripts/label/edit.js
+++ b/root/static/scripts/label/edit.js
@@ -7,6 +7,8 @@
* later version: http://www.gnu.org/licenses/gpl-2.0.txt
*/
+import '../edit/components/FormRowTextList.js';
+
import typeBubble from '../edit/typeBubble.js';
const typeIdField = 'select[name=edit-label\\.type_id]';
diff --git a/root/static/scripts/recording/edit.js b/root/static/scripts/recording/edit.js
index a886a01f142..f0ef8d56882 100644
--- a/root/static/scripts/recording/edit.js
+++ b/root/static/scripts/recording/edit.js
@@ -8,3 +8,4 @@
*/
import './RecordingName.js';
+import '../edit/components/FormRowTextList.js';
diff --git a/root/static/scripts/work/edit.js b/root/static/scripts/work/edit.js
index 51a90c8d894..adfaa7dba72 100644
--- a/root/static/scripts/work/edit.js
+++ b/root/static/scripts/work/edit.js
@@ -17,6 +17,8 @@ import {flushSync} from 'react-dom';
import * as ReactDOMClient from 'react-dom/client';
import {legacy_createStore as createStore} from 'redux';
+import '../edit/components/FormRowTextList.js';
+
import {LANGUAGE_MUL_ID, LANGUAGE_ZXX_ID} from '../common/constants.js';
import {groupBy} from '../common/utility/arrays.js';
import getScriptArgs from '../common/utility/getScriptArgs.js';
diff --git a/root/work/edit_form.tt b/root/work/edit_form.tt
index c98ebfd8a6a..958c24d4421 100644
--- a/root/work/edit_form.tt
+++ b/root/work/edit_form.tt
@@ -13,7 +13,13 @@
[%- form_row_text_long(r, 'comment', add_colon(l('Disambiguation'))) -%]
[%- form_row_select(r, 'type_id', add_colon(l('Type'))) -%]
- [%- form_row_text_list(r, 'iswcs', add_colon(l('ISWCs')), l('ISWC')) -%]
+ [% React.embed(c, 'static/scripts/edit/components/FormRowTextList', {
+ repeatable => form_to_json(form.field('iswcs')),
+ label => add_colon(l('ISWCs')),
+ addButtonId => 'add-iswc',
+ addButtonLabel => lp('Add ISWC', 'interactive'),
+ removeButtonLabel => lp('Remove ISWC', 'interactive'),
+ }) %]