diff --git a/scripts/scaffold-festival-days/src/api.ts b/scripts/scaffold-festival-days/src/api.ts index 2f5f9b7..3b9782a 100644 --- a/scripts/scaffold-festival-days/src/api.ts +++ b/scripts/scaffold-festival-days/src/api.ts @@ -46,6 +46,7 @@ export function getLinkedPlacesFromEvent(event: MBEvent): MBPlace[] { gid: placeGid, name: place.name, disambiguation: typeof place.disambiguation === 'string' ? place.disambiguation : undefined, + creditName: relation['target-credit'], }); } diff --git a/scripts/scaffold-festival-days/src/matrix-dialog.tsx b/scripts/scaffold-festival-days/src/matrix-dialog.tsx index 5442591..752df8d 100644 --- a/scripts/scaffold-festival-days/src/matrix-dialog.tsx +++ b/scripts/scaffold-festival-days/src/matrix-dialog.tsx @@ -96,7 +96,7 @@ export function MatrixDialog(props: { checked={isColumnChecked()} onChange={event => setPlaceSelected(place.gid, event.currentTarget.checked)} /> - {place.name} + {place.creditName ?? place.name} ); diff --git a/scripts/scaffold-festival-days/src/scaffold.ts b/scripts/scaffold-festival-days/src/scaffold.ts index 86d4d2e..29b1ba1 100644 --- a/scripts/scaffold-festival-days/src/scaffold.ts +++ b/scripts/scaffold-festival-days/src/scaffold.ts @@ -96,7 +96,7 @@ export async function scaffoldFestivalDays(params: { } for (const place of selectedPlaces) { - const venueName = `${event.name}: ${place.name}`; + const venueName = `${event.name}: ${place.creditName ?? place.name}`; const venueEventGid = await createSubEvent({ name: venueName, begin: singleDate, @@ -165,7 +165,7 @@ export async function scaffoldFestivalDays(params: { continue; } - const venueName = `${event.name}, ${dayWord} ${dayEvent.date.dayNumber}: ${place.name}`; + const venueName = `${event.name}, ${dayWord} ${dayEvent.date.dayNumber}: ${place.creditName ?? place.name}`; const venueEventGid = await createSubEvent({ name: venueName, begin: dayEvent.date, diff --git a/scripts/scaffold-festival-days/src/types.ts b/scripts/scaffold-festival-days/src/types.ts index 11cf705..a6e7844 100644 --- a/scripts/scaffold-festival-days/src/types.ts +++ b/scripts/scaffold-festival-days/src/types.ts @@ -42,6 +42,7 @@ export interface MBPlace { gid: string; name: string; disambiguation?: string; + creditName?: string; } export interface DateParts { diff --git a/scripts/scaffold-festival-days/src/ui.module.css b/scripts/scaffold-festival-days/src/ui.module.css index 6b31190..48d19aa 100644 --- a/scripts/scaffold-festival-days/src/ui.module.css +++ b/scripts/scaffold-festival-days/src/ui.module.css @@ -21,14 +21,44 @@ } .searchResultRow { - display: flex; + display: grid; + grid-template-columns: minmax(0, 1fr) 10em auto; align-items: center; - justify-content: space-between; gap: 0.5rem; } +.searchResultPlaceName { + min-width: 0; +} + +.creditNameInput { + box-sizing: border-box; + min-width: 0; +} + +.addPlaceButton { + width: 4.5em; + margin-left: 0 !important; + justify-self: start; +} + .placeOption { display: flex; align-items: center; gap: 0.35rem; } + +.actionsRow { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.dayWordControl { + display: flex; + align-items: center; +} + +.actionsButtons { + margin-left: auto; +} diff --git a/scripts/scaffold-festival-days/src/ui.tsx b/scripts/scaffold-festival-days/src/ui.tsx index f8e6d64..dc687b3 100644 --- a/scripts/scaffold-festival-days/src/ui.tsx +++ b/scripts/scaffold-festival-days/src/ui.tsx @@ -28,6 +28,7 @@ const CUSTOM_SENTINEL = '__custom__'; function ScaffoldFestivalUI(props: {event: MBEvent; places: MBPlace[]; dayWord: string}) { const [availablePlaces, setAvailablePlaces] = createSignal(props.places); const [searchResults, setSearchResults] = createSignal([]); + const [draftCreditNames, setDraftCreditNames] = createSignal>({}); const [placeInput, setPlaceInput] = createSignal(''); const [selectedPlaces, setSelectedPlaces] = createSignal>(new Set(props.places.map(place => place.gid))); const [isCreating, setIsCreating] = createSignal(false); @@ -84,9 +85,8 @@ function ScaffoldFestivalUI(props: {event: MBEvent; places: MBPlace[]; dayWord: if (placeGid) { const place = await fetchPlaceByGid(placeGid); if (place) { - addAndSelectPlace(place); - setSearchResults([]); - setStatus({message: `Added place: ${place.name}`, kind: 'info'}); + setSearchResults([place]); + setStatus({message: `Found: ${place.name}. Optionally set a credit name, then click Add.`, kind: 'info'}); } else { setStatus({message: 'Could not load place from provided link/MBID.', kind: 'error'}); } @@ -157,47 +157,6 @@ function ScaffoldFestivalUI(props: {event: MBEvent; places: MBPlace[]; dayWord: ? 'Select places to create direct per-place sub-events for this single-day festival.' : 'Select places to also create per-venue sub-events (optional).'}

- -
- - - { - const value = (e.target as HTMLInputElement).value; - setIsCustomDayWord(true); - setDayWord(value); - GM.setValue(DAY_WORD_STORAGE_KEY, value).catch(console.error); - }} - disabled={isCreating()} - style={{'margin-left': '4px', width: '6em'}} - /> - -
-
0}>
- {place => ( + {(place, index) => (
- - {place.name} - {disambiguation => {` (${disambiguation()})`}} + + + {place.name} + {disambiguation => {` (${disambiguation()})`}} + -
@@ -247,31 +229,75 @@ function ScaffoldFestivalUI(props: {event: MBEvent; places: MBPlace[]; dayWord:
{place => ( -
-
- +
+ +
+ + + { + const value = (e.target as HTMLInputElement).value; + setIsCustomDayWord(true); + setDayWord(value); + GM.setValue(DAY_WORD_STORAGE_KEY, value).catch(console.error); + }} + disabled={isCreating()} + style={{'margin-left': '4px', width: '6em'}} + /> + +
+
+
+ +
{ await page.unrouteAll(); }); + + test('uses place credit name when creating venue sub-events for multi-day festival', async ({ + page, + userscriptPage, + musicbrainzPage, + testFestivalEvent, + testPlaces, + }) => { + const placeIds = testPlaces.getAll(); + const placeCreditNames = ['Credit Name 1', 'Credit Name 2'] as const; + const routeState = await setupScaffoldRoutes({ + page, + userscriptPage, + testFestivalEvent, + testPlaces, + relations: placeIds.map((id, index) => ({ + 'target-type': 'place', + 'target-credit': placeCreditNames[index] ?? TEST_PLACE_NAMES[index], + place: { + id, + gid: id, + name: TEST_PLACE_NAMES[index], + }, + })), + }); + await musicbrainzPage.userscriptPage.goto(`/event/${testFestivalEvent.gid}`); + + await expect(page.getByRole('group', {name: 'dvirtz MusicBrainz scripts'})).toBeAttached(); + + const checkboxes = page.getByRole('checkbox'); + const count = await checkboxes.count(); + for (let i = 0; i < count; i++) { + await checkboxes.nth(i).check(); + } + + await confirmScaffoldCreation(page); + await expect(page.getByText('Festival days scaffolding complete!')).toBeAttached(); + + const dayCount = testFestivalEvent.getDates().length; + const venueEvents = routeState.createdEvents.filter(event => event.placeId !== null); + + expect(venueEvents).toHaveLength(dayCount * placeIds.length); + + for (let dayNumber = 1; dayNumber <= dayCount; dayNumber += 1) { + for (let i = 0; i < placeIds.length; i++) { + const creditName = placeCreditNames[i]!; + const placeId = placeIds[i]!; + const expectedName = `${TEST_FESTIVAL_NAME}, Day ${dayNumber}: ${creditName}`; + const match = venueEvents.find(event => event.name === expectedName && event.placeId === placeId); + expect(match).toBeDefined(); + } + } + + await page.unrouteAll(); + }); + + test('uses place credit name when creating per-place sub-events for single-day festival', async ({ + page, + userscriptPage, + musicbrainzPage, + testFestivalEvent, + testPlaces, + }) => { + const placeIds = testPlaces.getAll(); + const placeCreditNames = ['Credit Name 1', 'Credit Name 2'] as const; + const routeState = await setupScaffoldRoutes({ + page, + userscriptPage, + testFestivalEvent, + testPlaces, + endDate: testFestivalEvent.getBeginDate(), + relations: placeIds.map((id, index) => ({ + 'target-type': 'place', + 'target-credit': placeCreditNames[index] ?? TEST_PLACE_NAMES[index], + place: { + id, + gid: id, + name: TEST_PLACE_NAMES[index], + }, + })), + }); + await musicbrainzPage.userscriptPage.goto(`/event/${testFestivalEvent.gid}`); + + await expect(page.getByRole('group', {name: 'dvirtz MusicBrainz scripts'})).toBeAttached(); + + await confirmScaffoldCreation(page, false); + await expect(page.getByText('Festival days scaffolding complete!')).toBeAttached(); + + const venueEvents = routeState.createdEvents.filter(event => event.placeId !== null); + + expect(venueEvents).toHaveLength(placeIds.length); + + for (let i = 0; i < placeIds.length; i++) { + const creditName = placeCreditNames[i]!; + const placeId = placeIds[i]!; + const expectedName = `${TEST_FESTIVAL_NAME}: ${creditName}`; + const match = venueEvents.find(event => event.name === expectedName && event.placeId === placeId); + expect(match).toBeDefined(); + } + + await page.unrouteAll(); + }); });