Skip to content

Commit 42ef87a

Browse files
committed
feat: add tracks
This adds the ability to specify a track or room for a session so there can be multiple sessions in parallel
1 parent 0d199e8 commit 42ef87a

File tree

5 files changed

+222
-162
lines changed

5 files changed

+222
-162
lines changed

src/App.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ if (typeof window.matchMedia === 'function') {
2323
defaultTheme = match.matches ? Theme.dark : Theme.light
2424
}
2525

26+
export type Sessions = Record<string, string>
27+
2628
export type Schedule = {
2729
name: string
2830
day: string
2931
tz: string
30-
sessions: Record<number, string>
32+
sessions: Sessions
3133
hidePastSessions: boolean
3234
}
3335

@@ -46,6 +48,7 @@ export const App = () => {
4648
1430: 'Session 3',
4749
1530: 'Coffee Break',
4850
1545: 'Session 4',
51+
'1545@Main hall': `You can have sessions at the same time, too!`,
4952
1645: 'Coffee Break',
5053
1700: 'Closing & Retro',
5154
1730: 'Dinner Break',
@@ -137,17 +140,21 @@ export const App = () => {
137140
</div>
138141
<Editor
139142
onAdd={(add) => {
140-
updateSessions((sessions) => ({
141-
...sessions,
142-
[parseInt(`${add.hour}${add.minute}`, 10)]: `${add.name}${
143-
add.url === '' ? '' : `|${add.url.toString()}`
144-
}`,
145-
}))
143+
updateSessions((sessions) => {
144+
let time = parseInt(`${add.hour}${add.minute}`, 10).toString()
145+
if (add.track.length > 0) time = `${time}@${add.track}`
146+
return {
147+
...sessions,
148+
[time]: `${add.name}${
149+
add.url === '' ? '' : `|${add.url.toString()}`
150+
}`,
151+
}
152+
})
146153
}}
147154
onDelete={(time) => {
148155
updateSessions((sessions) => {
149156
const s = { ...sessions }
150-
delete (s as { [key: number]: string })[time]
157+
delete (s as { [key: string]: string })[time]
151158
return s
152159
})
153160
}}

src/Editor.tsx

Lines changed: 136 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Sessions } from 'app/App'
12
import { AddIcon, DeleteIcon } from 'app/FeatherIcons'
23
import formStyles from 'app/Form.module.css'
34
import { SessionName } from 'app/SessionName'
@@ -10,6 +11,7 @@ type AddSession = {
1011
hour: string
1112
minute: string
1213
url: string
14+
track: string
1315
}
1416

1517
const toNumber = (n: string) => {
@@ -31,15 +33,16 @@ export const Editor = ({
3133
}: {
3234
conferenceDate: string
3335
eventTimezoneName: string
34-
sessions: { [key: number]: string }
36+
sessions: Sessions
3537
onAdd: (newSession: AddSession) => void
36-
onDelete: (time: number) => void
38+
onDelete: (timeWithTrackA: string) => void
3739
}) => {
3840
const [add, updateAdd] = useState<AddSession>({
3941
name: '',
4042
hour: '19',
4143
minute: '45',
4244
url: '',
45+
track: '',
4346
})
4447
const inputRef = useRef<HTMLInputElement>(null)
4548
const isInputValid = () => {
@@ -67,12 +70,13 @@ export const Editor = ({
6770
...add,
6871
name: '',
6972
url: '',
73+
track: '',
7074
})
7175
inputRef.current?.focus()
7276
}
7377

7478
return (
75-
<>
79+
<form className={formStyles.Form}>
7680
<table className={tableStyles.Table}>
7781
<thead>
7882
<tr>
@@ -95,113 +99,143 @@ export const Editor = ({
9599
</button>
96100
</td>
97101
<td className="time">
98-
<input
99-
className={formStyles.NumberInput}
100-
ref={inputRef}
101-
type="text"
102-
inputMode="numeric"
103-
value={add.hour}
104-
onChange={({ target: { value } }) => {
105-
updateAdd({
106-
...add,
107-
hour: value,
108-
})
109-
}}
110-
name="session-hour"
111-
/>
112-
{':'}
113-
<input
114-
className={formStyles.NumberInput}
115-
type="text"
116-
inputMode="numeric"
117-
value={add.minute}
118-
onChange={({ target: { value } }) => {
119-
updateAdd({
120-
...add,
121-
minute: value,
122-
})
123-
}}
124-
name="session-minute"
125-
/>
102+
<fieldset>
103+
<legend>Local time</legend>
104+
<input
105+
className={formStyles.NumberInput}
106+
ref={inputRef}
107+
type="text"
108+
inputMode="numeric"
109+
value={add.hour}
110+
onChange={({ target: { value } }) => {
111+
updateAdd({
112+
...add,
113+
hour: value,
114+
})
115+
}}
116+
name="session-hour"
117+
maxLength={2}
118+
/>
119+
{':'}
120+
<input
121+
className={formStyles.NumberInput}
122+
type="text"
123+
inputMode="numeric"
124+
value={add.minute}
125+
onChange={({ target: { value } }) => {
126+
updateAdd({
127+
...add,
128+
minute: value,
129+
})
130+
}}
131+
name="session-minute"
132+
maxLength={2}
133+
/>
134+
</fieldset>
135+
<fieldset>
136+
<label htmlFor="track">Track/Room</label>
137+
<input
138+
className={formStyles.TextInput}
139+
type="text"
140+
value={add.track ?? ''}
141+
id={'track'}
142+
onChange={({ target: { value } }) =>
143+
updateAdd({
144+
...add,
145+
track: value,
146+
})
147+
}
148+
placeholder='e.g. "Main room"'
149+
name="session-track"
150+
/>
151+
</fieldset>
126152
</td>
127153
<td>
128-
<form className={formStyles.Form}>
129-
<fieldset>
130-
<label htmlFor="name">Session name</label>
131-
<input
132-
className={formStyles.TextInput}
133-
type="text"
134-
value={add.name}
135-
id={'name'}
136-
onKeyUp={({ key }) => {
137-
if (key === 'Enter') {
138-
if (isInputValid()) {
139-
addAction(add)
140-
onAdd(add)
141-
}
154+
<fieldset>
155+
<label htmlFor="name">Session name</label>
156+
<input
157+
className={formStyles.TextInput}
158+
type="text"
159+
value={add.name}
160+
id={'name'}
161+
onKeyUp={({ key }) => {
162+
if (key === 'Enter') {
163+
if (isInputValid()) {
164+
addAction(add)
165+
onAdd(add)
142166
}
143-
}}
144-
onChange={({ target: { value } }) =>
145-
updateAdd({
146-
...add,
147-
name: value,
148-
})
149-
}
150-
placeholder='e.g. "Intro Session"'
151-
name="session-name"
152-
/>
153-
</fieldset>
154-
<fieldset>
155-
<label htmlFor="url">
156-
Optional: URL to use as a hyperlink.
157-
</label>
158-
<input
159-
className={formStyles.TextInput}
160-
type="url"
161-
id="url"
162-
value={add.url ?? ''}
163-
onChange={({ target: { value } }) =>
164-
updateAdd({
165-
...add,
166-
url: value,
167-
})
168167
}
169-
onKeyUp={({ key }) => {
170-
if (key === 'Enter') {
171-
if (isInputValid()) {
172-
addAction(add)
173-
onAdd(add)
174-
}
168+
}}
169+
onChange={({ target: { value } }) =>
170+
updateAdd({
171+
...add,
172+
name: value,
173+
})
174+
}
175+
placeholder='e.g. "Intro Session"'
176+
name="session-name"
177+
/>
178+
</fieldset>
179+
<fieldset>
180+
<label htmlFor="url">
181+
Optional: URL to use as a hyperlink.
182+
</label>
183+
<input
184+
className={formStyles.TextInput}
185+
type="url"
186+
id="url"
187+
value={add.url ?? ''}
188+
onChange={({ target: { value } }) =>
189+
updateAdd({
190+
...add,
191+
url: value,
192+
})
193+
}
194+
onKeyUp={({ key }) => {
195+
if (key === 'Enter') {
196+
if (isInputValid()) {
197+
addAction(add)
198+
onAdd(add)
175199
}
176-
}}
177-
placeholder='e.g. "https://example.com/"'
178-
/>
179-
</fieldset>
180-
</form>
200+
}
201+
}}
202+
placeholder='e.g. "https://example.com/"'
203+
/>
204+
</fieldset>
181205
</td>
182206
</tr>
183-
{Object.entries(sessions).map(([time, name]) => (
184-
<tr key={time}>
185-
<td>
186-
<button
187-
className={formStyles.DeleteButton}
188-
onClick={() => {
189-
onDelete(time as unknown as number)
190-
}}
191-
>
192-
<DeleteIcon />
193-
</button>
194-
</td>
195-
<td className={'time'}>
196-
{formatEventTime(eventTime(time as unknown as number))}
197-
</td>
198-
<td>
199-
<SessionName name={name} />
200-
</td>
201-
</tr>
202-
))}
207+
{Object.entries(sessions)
208+
.sort(
209+
([timeWithTrackA], [timeWithTrackB]) =>
210+
parseInt(timeWithTrackA.split('@')[0]) -
211+
parseInt(timeWithTrackB.split('@')[0]),
212+
)
213+
.map(([timeWithTrack, name]) => {
214+
const [time, track] = timeWithTrack.split('@')
215+
return (
216+
<tr key={timeWithTrack}>
217+
<td>
218+
<button
219+
className={formStyles.DeleteButton}
220+
onClick={() => {
221+
onDelete(timeWithTrack)
222+
}}
223+
>
224+
<DeleteIcon />
225+
</button>
226+
</td>
227+
<td className={'time'}>
228+
{formatEventTime(eventTime(parseInt(time, 10)))}
229+
{track === undefined ? '' : ` @ ${track}`}
230+
</td>
231+
<td>
232+
<SessionName name={name} />
233+
</td>
234+
</tr>
235+
)
236+
})}
203237
</tbody>
204238
</table>
205-
</>
239+
</form>
206240
)
207241
}

src/Schedule.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,21 @@ export const Schedule = ({
105105
{formatTimezone(userTimeZone)} ({userFormat(currentTime)})
106106
</small>
107107
</th>
108+
<th>Track/Room</th>
108109
<th>Starts in</th>
109110
<th>Session</th>
110111
</tr>
111112
</thead>
112113
<tbody>
113114
{Object.entries(sessions)
115+
.sort(
116+
([timeWithTrackA], [timeWithTrackB]) =>
117+
parseInt(timeWithTrackA.split('@')[0]) -
118+
parseInt(timeWithTrackB.split('@')[0]),
119+
)
114120
.map((session, i, sessions) => {
115-
const time = session[0] as unknown as number
121+
const timeWithTrack = session[0]
122+
const time = parseInt(timeWithTrack.split('@')[0])
116123

117124
const nextIsOngoing =
118125
sessions[i + 1] !== undefined
@@ -127,15 +134,17 @@ export const Schedule = ({
127134
return { session, isPast, isOngoing }
128135
})
129136
.filter(({ isPast }) => (hidePastSessions ? !isPast : true))
130-
.map(({ session: [time, name], isOngoing }) => {
137+
.map(({ session: [timeWithTrack, name], isOngoing }) => {
138+
const [time, track] = timeWithTrack.split('@')
131139
return (
132-
<tr key={time} className={isOngoing ? 'ongoing' : ''}>
140+
<tr key={timeWithTrack} className={isOngoing ? 'ongoing' : ''}>
133141
<td className={'time'}>
134142
{formatEventTime(eventTime(time as unknown as number))}
135143
</td>
136144
<td className={'time'}>
137145
{userFormat(userTime(time as unknown as number))}
138146
</td>
147+
<td>{track ?? '—'}</td>
139148
{isOngoing && (
140149
<td>
141150
<em>ongoing</em>

src/Table.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
width: 10%;
4848
white-space: nowrap;
4949
}
50+
.Table td:global(.time) legend {
51+
text-align: left;
52+
margin-bottom: 0.5rem;
53+
}
5054
.Table td:global(.hot) {
5155
background-color: var(--color-countdownWarning);
5256
}

0 commit comments

Comments
 (0)