Optimise editor component

This commit is contained in:
Benji Grant 2023-06-09 01:58:02 +10:00
parent f72204c796
commit 30d50b0b0a
4 changed files with 37 additions and 55 deletions

View file

@ -1,13 +1,14 @@
import { useMemo } from 'react'
import { createPalette } from 'hue-map' import { createPalette } from 'hue-map'
import { useStore } from '/src/stores' import { useStore } from '/src/stores'
import useSettingsStore from '/src/stores/settingsStore' import useSettingsStore from '/src/stores/settingsStore'
export const usePalette = (min: number, max: number) => { export const usePalette = (steps: number) => {
const colormap = useStore(useSettingsStore, state => state.colormap) const colormap = useStore(useSettingsStore, state => state.colormap)
return createPalette({ return useMemo(() => createPalette({
map: (colormap === undefined || colormap === 'crabfit') ? [[0, [247, 158, 0, 0]], [1, [247, 158, 0, 255]]] : colormap, map: (colormap === undefined || colormap === 'crabfit') ? [[0, [247, 158, 0, 0]], [1, [247, 158, 0, 255]]] : colormap,
steps: Math.max((max - min) + 1, 2), steps,
}).format() }).format(), [steps])
} }

View file

@ -1,12 +1,12 @@
import { Fragment, useCallback, useMemo, useRef, useState } from 'react' import { Fragment, useCallback, useMemo, useRef, useState } from 'react'
import { createPalette } from 'hue-map'
import Content from '/src/components/Content/Content' import Content from '/src/components/Content/Content'
import { useTranslation } from '/src/i18n/client' import { useTranslation } from '/src/i18n/client'
import { useStore } from '/src/stores' import { useStore } from '/src/stores'
import useSettingsStore from '/src/stores/settingsStore' import useSettingsStore from '/src/stores/settingsStore'
import { calculateColumns, calculateRows, convertTimesToDates, makeClass, serializeTime } from '/src/utils' import { calculateTable, makeClass } from '/src/utils'
import { usePalette } from '/hooks/usePalette'
import styles from '../AvailabilityViewer/AvailabilityViewer.module.scss' import styles from '../AvailabilityViewer/AvailabilityViewer.module.scss'
interface AvailabilityEditorProps { interface AvailabilityEditorProps {
@ -24,14 +24,12 @@ const AvailabilityEditor = ({
}: AvailabilityEditorProps) => { }: AvailabilityEditorProps) => {
const { t, i18n } = useTranslation('event') const { t, i18n } = useTranslation('event')
const timeFormat = useStore(useSettingsStore, state => state.timeFormat) const timeFormat = useStore(useSettingsStore, state => state.timeFormat) ?? '12h'
const colormap = useStore(useSettingsStore, state => state.colormap)
// Calculate rows and columns // Calculate table
const [dates, rows, columns] = useMemo(() => { const { rows, columns } = useMemo(() =>
const dates = convertTimesToDates(times, timezone) calculateTable(times, i18n.language, timeFormat, timezone),
return [dates, calculateRows(dates), calculateColumns(dates)] [times, i18n.language, timeFormat, timezone])
}, [times, timezone])
// Ref and state required to rerender but also access static version in callbacks // Ref and state required to rerender but also access static version in callbacks
const selectingRef = useRef<string[]>([]) const selectingRef = useRef<string[]>([])
@ -44,13 +42,8 @@ const AvailabilityEditor = ({
const startPos = useRef({ x: 0, y: 0 }) const startPos = useRef({ x: 0, y: 0 })
const mode = useRef<'add' | 'remove'>() const mode = useRef<'add' | 'remove'>()
// Is specific dates or just days of the week // Create the colour palette
const isSpecificDates = useMemo(() => times[0].length === 13, [times]) const palette = usePalette(2)
const palette = useMemo(() => createPalette({
map: colormap !== 'crabfit' ? colormap : [[0, [247, 158, 0, 0]], [1, [247, 158, 0, 255]]],
steps: 2,
}).format(), [colormap])
return <> return <>
<Content isCentered>{t('you.info')}</Content> <Content isCentered>{t('you.info')}</Content>
@ -89,8 +82,8 @@ const AvailabilityEditor = ({
<div className={styles.timeLabels}> <div className={styles.timeLabels}>
{rows.map((row, i) => {rows.map((row, i) =>
<div className={styles.timeSpace} key={i}> <div className={styles.timeSpace} key={i}>
{row && row.minute === 0 && <label className={styles.timeLabel}> {row && <label className={styles.timeLabel}>
{row.toLocaleString(i18n.language, { hour: 'numeric', hour12: timeFormat === '12h' })} {row.label}
</label>} </label>}
</div> </div>
)} )}
@ -98,43 +91,39 @@ const AvailabilityEditor = ({
{columns.map((column, x) => <Fragment key={x}> {columns.map((column, x) => <Fragment key={x}>
{column ? <div className={styles.dateColumn}> {column ? <div className={styles.dateColumn}>
{isSpecificDates && <label className={styles.dateLabel}>{column.toLocaleString(i18n.language, { month: 'short', day: 'numeric' })}</label>} {column.header.dateLabel && <label className={styles.dateLabel}>{column.header.dateLabel}</label>}
<label className={styles.dayLabel}>{column.toLocaleString(i18n.language, { weekday: 'short' })}</label> <label className={styles.dayLabel}>{column.header.weekdayLabel}</label>
<div <div
className={styles.times} className={styles.times}
data-border-left={x === 0 || columns.at(x - 1) === null} data-border-left={x === 0 || columns.at(x - 1) === null}
data-border-right={x === columns.length - 1 || columns.at(x + 1) === null} data-border-right={x === columns.length - 1 || columns.at(x + 1) === null}
> >
{rows.map((row, y) => { {column.cells.map((cell, y) => {
if (y === rows.length - 1) return null if (y === column.cells.length - 1) return null
if (!row || rows.at(y + 1) === null || dates.every(d => !d.equals(column.toZonedDateTime({ timeZone: timezone, plainTime: row })))) { if (!cell) return <div
return <div
className={makeClass(styles.timeSpace, styles.grey)} className={makeClass(styles.timeSpace, styles.grey)}
key={y} key={y}
title={t<string>('greyed_times')} title={t<string>('greyed_times')}
/> />
}
const date = column.toZonedDateTime({ timeZone: timezone, plainTime: row })
return <div return <div
key={y} key={y}
className={styles.time} className={styles.time}
style={{ style={{
backgroundColor: ( backgroundColor: (
(!(mode.current === 'remove' && selecting.includes(serializeTime(date, isSpecificDates))) && value.includes(serializeTime(date, isSpecificDates))) (!(mode.current === 'remove' && selecting.includes(cell.serialized)) && value.includes(cell.serialized))
|| (mode.current === 'add' && selecting.includes(serializeTime(date, isSpecificDates))) || (mode.current === 'add' && selecting.includes(cell.serialized))
) ? palette[1] : palette[0], ) ? palette[1] : palette[0],
...date.minute !== 0 && date.minute !== 30 && { borderTopColor: 'transparent' }, ...cell.minute !== 0 && cell.minute !== 30 && { borderTopColor: 'transparent' },
...date.minute === 30 && { borderTopStyle: 'dotted' }, ...cell.minute === 30 && { borderTopStyle: 'dotted' },
}} }}
onPointerDown={e => { onPointerDown={e => {
e.preventDefault() e.preventDefault()
startPos.current = { x, y } startPos.current = { x, y }
mode.current = value.includes(serializeTime(date, isSpecificDates)) ? 'remove' : 'add' mode.current = value.includes(cell.serialized) ? 'remove' : 'add'
setSelecting([serializeTime(date, isSpecificDates)]) setSelecting([cell.serialized])
e.currentTarget.releasePointerCapture(e.pointerId) e.currentTarget.releasePointerCapture(e.pointerId)
document.addEventListener('pointerup', () => { document.addEventListener('pointerup', () => {
@ -155,13 +144,9 @@ const AvailabilityEditor = ({
} }
} }
setSelecting(found.flatMap(d => { setSelecting(found.flatMap(d => {
const [time, date] = [rows[d.y], columns[d.x]] const serialized = columns[d.x]?.cells[d.y]?.serialized
if (time !== null && date !== null) { if (serialized && times.includes(serialized)) {
const str = serializeTime(date.toZonedDateTime({ timeZone: timezone, plainTime: time }), isSpecificDates) return [serialized]
if (times.includes(str)) {
return [str]
}
return []
} }
return [] return []
})) }))

View file

@ -49,14 +49,11 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps
[times, filteredPeople, people]) [times, filteredPeople, people])
// Create the colour palette // Create the colour palette
const palette = usePalette(min, max) const palette = usePalette(Math.max((max - min) + 1, 2))
// Is specific dates or just days of the week
const isSpecificDates = useMemo(() => times[0].length === 13, [times])
const heatmap = useMemo(() => columns.map((column, x) => <Fragment key={x}> const heatmap = useMemo(() => columns.map((column, x) => <Fragment key={x}>
{column ? <div className={styles.dateColumn}> {column ? <div className={styles.dateColumn}>
{isSpecificDates && <label className={styles.dateLabel}>{column.header.dateLabel}</label>} {column.header.dateLabel && <label className={styles.dateLabel}>{column.header.dateLabel}</label>}
<label className={styles.dayLabel}>{column.header.weekdayLabel}</label> <label className={styles.dayLabel}>{column.header.weekdayLabel}</label>
<div <div
@ -108,7 +105,6 @@ const AvailabilityViewer = ({ times, timezone, people }: AvailabilityViewerProps
</div> : <div className={styles.columnSpacer} />} </div> : <div className={styles.columnSpacer} />}
</Fragment>), [ </Fragment>), [
availabilities, availabilities,
isSpecificDates,
columns, columns,
highlight, highlight,
max, max,

View file

@ -28,7 +28,7 @@ export const calculateTable = (
columns: columns.map(column => column ? { columns: columns.map(column => column ? {
header: { header: {
dateLabel: column.toLocaleString(locale, { month: 'short', day: 'numeric' }), dateLabel: isSpecificDates ? column.toLocaleString(locale, { month: 'short', day: 'numeric' }) : undefined,
weekdayLabel: column.toLocaleString(locale, { weekday: 'short' }), weekdayLabel: column.toLocaleString(locale, { weekday: 'short' }),
string: column.toString(), string: column.toString(),
}, },