From 911aaad89055e5c0e4eafc640c55c0f27ae73a02 Mon Sep 17 00:00:00 2001 From: David Wheatley Date: Sat, 28 Oct 2023 16:10:10 +0100 Subject: [PATCH] feat: implement short platform announcements --- .../systems/stations/KeTechPhil.ts | 414 ++++++++++++------ src/components/CallingAtSelector.tsx | 105 ++++- src/helpers/crsToStationItemMapper.ts | 15 +- 3 files changed, 378 insertions(+), 156 deletions(-) diff --git a/src/announcement-data/systems/stations/KeTechPhil.ts b/src/announcement-data/systems/stations/KeTechPhil.ts index 36ffa7b1e..4e50ffeed 100644 --- a/src/announcement-data/systems/stations/KeTechPhil.ts +++ b/src/announcement-data/systems/stations/KeTechPhil.ts @@ -1,9 +1,9 @@ import StationAnnouncementSystem from '@announcement-data/StationAnnouncementSystem' -import CallingAtSelector from '@components/CallingAtSelector' +import CallingAtSelector, { CallingAtPoint } from '@components/CallingAtSelector' import CustomAnnouncementPane, { ICustomAnnouncementPreset } from '@components/PanelPanes/CustomAnnouncementPane' import CustomButtonPane from '@components/PanelPanes/CustomButtonPane' import { AllStationsTitleValueMap } from '@data/StationManipulators' -import crsToStationItemMapper from '@helpers/crsToStationItemMapper' +import crsToStationItemMapper, { stationItemCompleter } from '@helpers/crsToStationItemMapper' import { AudioItem, CustomAnnouncementTab } from '../../AnnouncementSystem' type ChimeType = /*'3' |*/ 'four' | 'none' @@ -15,152 +15,196 @@ interface INextTrainAnnouncementOptions { min: string toc: string terminatingStationCode: string - vias: { crsCode: string; name: string; randomId: string }[] - callingAt: { crsCode: string; name: string; randomId: string }[] + vias: CallingAtPoint[] + callingAt: CallingAtPoint[] coaches: string } -const AnnouncementPresets: Readonly> = { - nextTrain: [ - { - name: '12:28 | SN Littlehampton to Brighton', - state: { - chime: 'four', - platform: '2', - hour: '12', - min: '28', - toc: 'southern', - terminatingStationCode: 'BTN', - vias: [], - callingAt: ['ANG', 'GBS', 'DUR', 'WWO', 'WRH', 'SWK', 'PLD', 'HOV'].map(crsToStationItemMapper), - coaches: '8 coaches', - }, - }, - { - name: '17:15 | GX Brighton to London Victoria', - state: { - chime: 'four', - platform: '5', - hour: '17', - min: '15', - toc: 'gatwick express', - terminatingStationCode: 'VIC', - vias: ['GTW'].map(crsToStationItemMapper), - callingAt: ['PRP', 'HSK', 'BUG', 'HHE', 'GTW'].map(crsToStationItemMapper), - coaches: '8 coaches', - }, - }, - { - name: '11:18 | VT Euston to Edinburgh', - state: { - chime: 'four', - platform: '6', - hour: '11', - min: '18', - toc: 'virgin pendolino', - terminatingStationCode: 'EDB', - vias: ['BHM'].map(crsToStationItemMapper), - callingAt: ['MKC', 'RUG', 'COV', 'BHI', 'BHM', 'SAD', 'WVH', 'STA', 'CRE', 'WBQ', 'WGN', 'PRE', 'LAN', 'PNR', 'CAR', 'HYM'].map( - crsToStationItemMapper, - ), - coaches: '11 coaches', - }, - }, - { - name: '08:20 | XC Aberdeen to Penzance', - state: { - chime: 'four', - platform: '3', - hour: '08', - min: '20', - toc: 'crosscountry', - terminatingStationCode: 'PNZ', - vias: ['LDS'].map(crsToStationItemMapper), - callingAt: [ - 'STN', - 'MTS', - 'ARB', - 'DEE', - 'LEU', - 'CUP', - 'LDY', - 'MNC', - 'KDY', - 'INK', - 'HYM', - 'EDB', - 'BWK', - 'ALM', - 'NCL', - 'DHM', - 'DAR', - 'YRK', - 'LDS', - 'WKF', - 'SHF', - 'DBY', - 'BUT', - 'BHM', - 'CNM', - 'BPW', - 'BRI', - 'TAU', - 'TVP', - 'EXD', - 'NTA', - 'TOT', - 'PLY', - 'LSK', - 'BOD', - 'SAU', - 'TRU', - 'RED', - 'SER', - ].map(crsToStationItemMapper), - coaches: '5 coaches', - }, - }, - { - // http://www.1s76.com/1S76%202008.htm - name: '08:20 | 1O23 XC Manchester to Brighton (2008)', - state: { - chime: 'four', - platform: '3', - hour: '08', - min: '20', - toc: 'crosscountry', - terminatingStationCode: 'BTN', - vias: ['BHM', 'KPA'].map(crsToStationItemMapper), - callingAt: ['SPT', 'MAC', 'CNG', 'SOT', 'WVH', 'BHM', 'LMS', 'BAN', 'OXF', 'RDG', 'KPA', 'ECR', 'GTW', 'HHE'].map( - crsToStationItemMapper, - ), - coaches: '5 coaches', - }, - }, - { - name: '18:07 | Chiltern MYB - Stourbridge', - state: { - chime: 'four', - platform: '2', - hour: '18', - min: '07', - toc: 'chiltern railways', - terminatingStationCode: 'SBJ', - vias: [], - callingAt: ['HDM', 'BCS', 'BAN', 'LMS', 'WRW', 'WRP', 'DDG', 'SOL', 'BMO', 'BSW', 'ROW'].map(crsToStationItemMapper), - coaches: '5 coaches', - }, - }, - ], - standingTrain: [], -} - export default class KeTechPhil extends StationAnnouncementSystem { readonly NAME = 'KeTech - Phil Sayer' readonly ID = 'KETECH_PHIL_V1' readonly FILE_PREFIX = 'station/ketech/phil' readonly SYSTEM_TYPE = 'station' + private get announcementPresets(): Readonly> { + return { + nextTrain: [ + { + name: '12:28 | SN Littlehampton to Brighton', + state: { + chime: 'four', + platform: '2', + hour: '12', + min: '28', + toc: 'southern', + terminatingStationCode: 'BTN', + vias: [], + callingAt: ['ANG', 'GBS', 'DUR', 'WWO', 'WRH', 'SWK', 'PLD', 'HOV'].map(crsToStationItemMapper), + coaches: '8 coaches', + }, + }, + { + name: '17:15 | GX Brighton to London Victoria', + state: { + chime: 'four', + platform: '5', + hour: '17', + min: '15', + toc: 'gatwick express', + terminatingStationCode: 'VIC', + vias: ['GTW'].map(crsToStationItemMapper), + callingAt: ['PRP', 'HSK', 'BUG', 'HHE', 'GTW'].map(crsToStationItemMapper), + coaches: '8 coaches', + }, + }, + { + name: '11:18 | VT Euston to Edinburgh', + state: { + chime: 'four', + platform: '6', + hour: '11', + min: '18', + toc: 'virgin pendolino', + terminatingStationCode: 'EDB', + vias: ['BHM'].map(crsToStationItemMapper), + callingAt: [ + 'MKC', + 'RUG', + 'COV', + 'BHI', + 'BHM', + 'SAD', + 'WVH', + 'STA', + 'CRE', + 'WBQ', + 'WGN', + 'PRE', + 'LAN', + 'PNR', + 'CAR', + { crsCode: 'HYM', shortPlatform: this.getValueForShortPlatform('front 10 coaches') }, + ].map(stationItemCompleter), + coaches: '11 coaches', + }, + }, + { + name: '08:20 | XC Aberdeen to Penzance', + state: { + chime: 'four', + platform: '3', + hour: '08', + min: '20', + toc: 'crosscountry', + terminatingStationCode: 'PNZ', + vias: ['LDS'].map(crsToStationItemMapper), + callingAt: [ + 'STN', + 'MTS', + 'ARB', + 'DEE', + 'LEU', + 'CUP', + 'LDY', + 'MNC', + 'KDY', + 'INK', + 'HYM', + 'EDB', + 'BWK', + 'ALM', + 'NCL', + 'DHM', + 'DAR', + 'YRK', + 'LDS', + 'WKF', + 'SHF', + 'DBY', + 'BUT', + 'BHM', + 'CNM', + 'BPW', + 'BRI', + 'TAU', + 'TVP', + 'EXD', + 'NTA', + 'TOT', + 'PLY', + 'LSK', + 'BOD', + 'SAU', + 'TRU', + 'RED', + 'SER', + ].map(crsToStationItemMapper), + coaches: '5 coaches', + }, + }, + { + // http://www.1s76.com/1S76%202008.htm + name: '08:20 | 1O23 XC Manchester to Brighton (2008)', + state: { + chime: 'four', + platform: '3', + hour: '08', + min: '20', + toc: 'crosscountry', + terminatingStationCode: 'BTN', + vias: ['BHM', 'KPA'].map(crsToStationItemMapper), + callingAt: ['SPT', 'MAC', 'CNG', 'SOT', 'WVH', 'BHM', 'LMS', 'BAN', 'OXF', 'RDG', 'KPA', 'ECR', 'GTW', 'HHE'].map( + crsToStationItemMapper, + ), + coaches: '5 coaches', + }, + }, + { + name: '18:07 | Chiltern MYB - Stourbridge', + state: { + chime: 'four', + platform: '2', + hour: '18', + min: '07', + toc: 'chiltern railways', + terminatingStationCode: 'SBJ', + vias: [], + callingAt: ['HDM', 'BCS', 'BAN', 'LMS', 'WRW', 'WRP', 'DDG', 'SOL', 'BMO', 'BSW', 'ROW'].map(crsToStationItemMapper), + coaches: '5 coaches', + }, + }, + { + name: '12:50 | SN Eastbourne - Ashford', + state: { + chime: 'four', + platform: '2', + hour: '12', + min: '50', + toc: 'southern', + terminatingStationCode: 'AFK', + vias: [], + callingAt: [ + 'HMD', + 'COB', + 'PEV', + 'CLL', + 'BEX', + 'SLQ', + 'HGS', + 'ORE', + { crsCode: 'TOK', shortPlatform: this.getValueForShortPlatform('front coach') }, + 'WSE', + 'RYE', + { crsCode: 'APD', shortPlatform: this.getValueForShortPlatform('front 2 coaches') }, + 'HMT', + ].map(stationItemCompleter), + coaches: '3 coaches', + }, + }, + ], + } + } + private AVAILABLE_TOCS = [ 'a replacement bus', 'additional', @@ -371,6 +415,51 @@ export default class KeTechPhil extends StationAnnouncementSystem { return files } + private async getShortPlatforms(callingPoints: CallingAtPoint[]): Promise { + const files: AudioItem[] = [] + + const shortPlatforms = callingPoints.reduce( + (acc, curr) => { + if (!curr.shortPlatform) return acc + + if (acc[curr.shortPlatform]) { + acc[curr.shortPlatform].push(curr.crsCode) + } else { + acc[curr.shortPlatform] = [curr.crsCode] + } + + return acc + }, + {} as Record, + ) + + const order = this.shortPlatforms.map(x => x.value) + + let firstAdded = false + + order.forEach(s => { + if (!shortPlatforms[s]) return + + files.push( + { id: firstAdded ? 's.customers for' : 's.due to short platforms customers for', opts: { delayStart: 200 } }, + ...this.pluraliseAudio( + shortPlatforms[s].map(crs => ({ id: crs, opts: { delayStart: 100 } })), + 100, + { + prefix: 'station.m.', + finalPrefix: 'station.m.', + andId: 'm.and', + }, + ), + ...s.split(','), + ) + + firstAdded = true + }) + + return files + } + private async playNextTrainAnnouncement(options: INextTrainAnnouncementOptions, download: boolean = false): Promise { const files: AudioItem[] = [] @@ -406,6 +495,8 @@ export default class KeTechPhil extends StationAnnouncementSystem { ) } + files.push(...(await this.getShortPlatforms(options.callingAt))) + const coaches = options.coaches.split(' ')[0] // Platforms share the same audio as coach numbers @@ -2946,13 +3037,55 @@ export default class KeTechPhil extends StationAnnouncementSystem { 'ZFD', 'ZLW', ] + private shortPlatforms = [ + { value: 'e.should travel in the front coach of the train', title: 'front coach' }, + { value: 'm.should travel in the front,platform.s.2,e.coaches of the train', title: 'front 2 coaches' }, + { value: 'm.should travel in the front,platform.s.3,e.coaches of the train', title: 'front 3 coaches' }, + { value: 'm.should travel in the front,platform.s.4,e.coaches of the train', title: 'front 4 coaches' }, + { value: 'm.should travel in the front,platform.s.5,e.coaches of the train', title: 'front 5 coaches' }, + { value: 'm.should travel in the front,platform.s.6,e.coaches of the train', title: 'front 6 coaches' }, + { value: 'm.should travel in the front,platform.s.7,e.coaches of the train', title: 'front 7 coaches' }, + { value: 'm.should travel in the front,platform.s.8,e.coaches of the train', title: 'front 8 coaches' }, + { value: 'm.should travel in the front,platform.s.9,e.coaches of the train', title: 'front 9 coaches' }, + { value: 'm.should travel in the front,platform.s.10,e.coaches of the train', title: 'front 10 coaches' }, + { value: 'm.should travel in the front,platform.s.11,e.coaches of the train', title: 'front 11 coaches' }, + { value: 'm.should travel in the front,platform.s.12,e.coaches of the train', title: 'front 12 coaches' }, + { value: 'm.should travel in the middle coach of the train', title: 'middle coach' }, + { value: 'm.should travel in the middle,platform.s.2,e.coaches of the train', title: 'middle 2 coaches' }, + { value: 'm.should travel in the middle,platform.s.3,e.coaches of the train', title: 'middle 3 coaches' }, + { value: 'm.should travel in the middle,platform.s.4,e.coaches of the train', title: 'middle 4 coaches' }, + { value: 'm.should travel in the middle,platform.s.5,e.coaches of the train', title: 'middle 5 coaches' }, + { value: 'm.should travel in the middle,platform.s.6,e.coaches of the train', title: 'middle 6 coaches' }, + { value: 'm.should travel in the middle,platform.s.7,e.coaches of the train', title: 'middle 7 coaches' }, + { value: 'm.should travel in the middle,platform.s.8,e.coaches of the train', title: 'middle 8 coaches' }, + { value: 'm.should travel in the middle,platform.s.9,e.coaches of the train', title: 'middle 9 coaches' }, + { value: 'm.should travel in the middle,platform.s.10,e.coaches of the train', title: 'middle 10 coaches' }, + { value: 'm.should travel in the middle,platform.s.11,e.coaches of the train', title: 'middle 11 coaches' }, + { value: 'm.should travel in the middle,platform.s.12,e.coaches of the train', title: 'middle 12 coaches' }, + { value: 'm.should travel in the rear coach of the train', title: 'rear coach' }, + { value: 'm.should travel in the rear,platform.s.2,e.coaches of the train', title: 'rear 2 coaches' }, + { value: 'm.should travel in the rear,platform.s.3,e.coaches of the train', title: 'rear 3 coaches' }, + { value: 'm.should travel in the rear,platform.s.4,e.coaches of the train', title: 'rear 4 coaches' }, + { value: 'm.should travel in the rear,platform.s.5,e.coaches of the train', title: 'rear 5 coaches' }, + { value: 'm.should travel in the rear,platform.s.6,e.coaches of the train', title: 'rear 6 coaches' }, + { value: 'm.should travel in the rear,platform.s.7,e.coaches of the train', title: 'rear 7 coaches' }, + { value: 'm.should travel in the rear,platform.s.8,e.coaches of the train', title: 'rear 8 coaches' }, + { value: 'm.should travel in the rear,platform.s.9,e.coaches of the train', title: 'rear 9 coaches' }, + { value: 'm.should travel in the rear,platform.s.10,e.coaches of the train', title: 'rear 10 coaches' }, + { value: 'm.should travel in the rear,platform.s.11,e.coaches of the train', title: 'rear 11 coaches' }, + { value: 'm.should travel in the rear,platform.s.12,e.coaches of the train', title: 'rear 12 coaches' }, + ] + + private getValueForShortPlatform(title: string): string | null { + return this.shortPlatforms.find(p => p.title === title)?.value ?? null + } readonly customAnnouncementTabs: Record = { nextTrain: { name: 'Next train', component: CustomAnnouncementPane, props: { - presets: AnnouncementPresets.nextTrain, + presets: this.announcementPresets.nextTrain, playHandler: this.playNextTrainAnnouncement.bind(this), options: { chime: { @@ -3040,6 +3173,7 @@ export default class KeTechPhil extends StationAnnouncementSystem { component: CallingAtSelector, props: { availableStations: this.stations, + enableShortPlatforms: this.shortPlatforms, }, default: [], }, diff --git a/src/components/CallingAtSelector.tsx b/src/components/CallingAtSelector.tsx index 9a1006581..8a052b560 100644 --- a/src/components/CallingAtSelector.tsx +++ b/src/components/CallingAtSelector.tsx @@ -5,7 +5,7 @@ import { makeStyles } from '@material-ui/styles' import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd' import createOptionField from '@helpers/createOptionField' import { nanoid } from 'nanoid' -import { AllStationsTitleValueMap, getStationByCrs } from '@data/StationManipulators' +import { AllStationsTitleValueMap } from '@data/StationManipulators' const useStyles = makeStyles({ root: { @@ -34,29 +34,59 @@ const useStyles = makeStyles({ marginBottom: 8, display: 'flex', alignItems: 'center', + gap: 8, + }, + mainInfo: { + display: 'flex', + flexDirection: 'column', + padding: '4px 0', + gap: 8, - '& > button': { - boxSizing: 'content-box', - padding: 8, - display: 'inline-block', - height: '1em', - marginLeft: 8, + '& > :not(:first-child)': { + fontSize: 18, + }, - '& > svg': { - height: '1em', - }, + '& > *': { + padding: 0, + }, + }, + deleteButton: { + boxSizing: 'content-box', + padding: 8, + display: 'inline-block', + height: '1em', + marginLeft: 'auto', + + '& > svg': { + height: '1em', }, }, }) +export interface CallingAtPoint { + crsCode: string + name: string + randomId: string + shortPlatform?: string + requestStop?: boolean +} + interface ICallingAtSelectorProps { - value: { crsCode: string; name: string; randomId: string }[] - onChange: (newValue: { crsCode: string; name: string; randomId: string }[]) => void + value: CallingAtPoint[] + onChange: (newValue: CallingAtPoint[]) => void availableStations: string[] additionalOptions?: { title: string; value: string }[] selectLabel?: string placeholder?: string heading?: string + /** + * If false, short platforms will be disabled. If an array, only the specified platforms will be enabled. + */ + enableShortPlatforms?: false | { title: string; value: string }[] + /** + * Whether request stops are enabled. + */ + requestStops?: boolean } function CallingAtSelector({ @@ -67,6 +97,8 @@ function CallingAtSelector({ selectLabel = 'Intermediary stops', placeholder = 'Add a calling point...', heading = 'Calling at... (excluding terminating station)', + enableShortPlatforms = false, + requestStops = false, }: ICallingAtSelectorProps): JSX.Element { const classes = useStyles() @@ -94,7 +126,14 @@ function CallingAtSelector({ }, { onChange: newStop => { - onChange([...value, { crsCode: newStop, name: AvailableStations.find(s => s.value === newStop).title, randomId: nanoid() }]) + onChange([ + ...value, + { + crsCode: newStop, + name: AvailableStations.find(s => s.value === newStop).title, + randomId: nanoid(), + }, + ]) }, value: '', key: 'intermediaryStationCode', @@ -132,12 +171,48 @@ function CallingAtSelector({ {...provided.draggableProps} {...provided.dragHandleProps} > - {stop.name}{' '} +
+ {stop.name} + + {enableShortPlatforms && + createOptionField( + { + default: 'none', + options: [{ value: '', title: 'No' }].concat(enableShortPlatforms), + name: 'Short platform', + type: 'select', + }, + { + value: stop.shortPlatform || '', + key: 'shortPlatform', + onChange(v) { + onChange(value.map(s => (s.randomId === stop.randomId ? { ...s, shortPlatform: v } : s))) + }, + }, + )} + + {requestStops && + createOptionField( + { + default: false, + name: 'Request stop', + type: 'boolean', + }, + { + value: stop.requestStop || false, + key: 'requestStop', + onChange(v) { + onChange(value.map(s => (s.randomId === stop.randomId ? { ...s, requestStop: v } : s))) + }, + }, + )} +
+