Optimise editor component
This commit is contained in:
parent
f72204c796
commit
30d50b0b0a
|
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 []
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue