diff --git a/crabfit-frontend/package.json b/crabfit-frontend/package.json index ce47a8c..6e59f62 100644 --- a/crabfit-frontend/package.json +++ b/crabfit-frontend/package.json @@ -14,6 +14,7 @@ "dayjs": "^1.11.5", "gapi-script": "^1.2.0", "goober": "^2.1.10", + "hue-map": "^0.1.1", "i18next": "^21.9.0", "i18next-browser-languagedetector": "^6.1.5", "i18next-http-backend": "^1.4.1", diff --git a/crabfit-frontend/public/i18n/en/common.json b/crabfit-frontend/public/i18n/en/common.json index 3081de2..0972bf2 100644 --- a/crabfit-frontend/public/i18n/en/common.json +++ b/crabfit-frontend/public/i18n/en/common.json @@ -53,6 +53,10 @@ }, "language": { "label": "Language" + }, + "colormap": { + "label": "Heatmap colors", + "classic": "Crab Fit (classic)" } }, "video": { diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx index 25008f4..c863501 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx @@ -4,6 +4,7 @@ import dayjs from 'dayjs' import localeData from 'dayjs/plugin/localeData' import customParseFormat from 'dayjs/plugin/customParseFormat' import relativeTime from 'dayjs/plugin/relativeTime' +import createPalette from 'hue-map' import { useSettingsStore, useLocaleUpdateStore } from '/src/stores' @@ -50,6 +51,7 @@ const AvailabilityViewer = ({ const [tooltip, setTooltip] = useState(null) const timeFormat = useSettingsStore(state => state.timeFormat) const highlight = useSettingsStore(state => state.highlight) + const colormap = useSettingsStore(state => state.colormap) const [filteredPeople, setFilteredPeople] = useState([]) const [touched, setTouched] = useState(false) const [tempFocus, setTempFocus] = useState(null) @@ -65,6 +67,13 @@ const AvailabilityViewer = ({ setTouched(people.length <= 1) }, [people]) + const [palette, setPalette] = useState([]) + + useEffect(() => setPalette(createPalette({ + map: colormap === 'crabfit' ? [[0, [247,158,0,0]], [1, [247,158,0,255]]] : colormap, + steps: tempFocus !== null ? 2 : Math.min(max, filteredPeople.length)+1, + })), [tempFocus, filteredPeople, max, colormap]) + const heatmap = useMemo(() => ( @@ -104,7 +113,8 @@ const AvailabilityViewer = ({ key={i} $time={time} className="time" - $peopleCount={focusCount !== null && focusCount !== peopleHere.length ? 0 : peopleHere.length} + $peopleCount={focusCount !== null && focusCount !== peopleHere.length ? null : peopleHere.length} + $palette={palette} aria-label={peopleHere.join(', ')} $maxPeople={tempFocus !== null ? 1 : Math.min(max, filteredPeople.length)} $minPeople={tempFocus !== null ? 0 : Math.min(min, filteredPeople.length)} @@ -129,9 +139,7 @@ const AvailabilityViewer = ({ })} - {last && dates.length !== i+1 && ( - - )} + {last && dates.length !== i+1 && } ) })} @@ -151,6 +159,7 @@ const AvailabilityViewer = ({ timeFormat, timeLabels, times, + palette, ]) return ( diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js index 59f99f3..ec0c444 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js @@ -86,15 +86,15 @@ export const Time = styled('div')` border-top: 2px dotted var(--text); `} - background-color: ${props => `#F79E00${Math.round((props.$peopleCount/props.$maxPeople)*255).toString(16)}`}; + background-color: ${props => props.$palette[props.$peopleCount] ?? 'transparent'}; ${props => props.$highlight && props.$peopleCount === props.$maxPeople && props.$peopleCount > 0 && ` background-image: repeating-linear-gradient( 45deg, transparent, transparent 4.3px, - var(--shadow) 4.3px, - var(--shadow) 8.6px + rgba(0,0,0,.5) 4.3px, + rgba(0,0,0,.5) 8.6px ); `} diff --git a/crabfit-frontend/src/components/Legend/Legend.jsx b/crabfit-frontend/src/components/Legend/Legend.jsx index 53f5632..c124455 100644 --- a/crabfit-frontend/src/components/Legend/Legend.jsx +++ b/crabfit-frontend/src/components/Legend/Legend.jsx @@ -1,4 +1,6 @@ +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' +import createPalette from 'hue-map' import { useSettingsStore } from '/src/stores' @@ -17,8 +19,16 @@ const Legend = ({ }) => { const { t } = useTranslation('event') const highlight = useSettingsStore(state => state.highlight) + const colormap = useSettingsStore(state => state.colormap) const setHighlight = useSettingsStore(state => state.setHighlight) + const [palette, setPalette] = useState([]) + + useEffect(() => setPalette(createPalette({ + map: colormap === 'crabfit' ? [[0, [247,158,0,0]], [1, [247,158,0,255]]] : colormap, + steps: max+1-min, + })), [min, max, colormap]) + return ( @@ -31,7 +41,7 @@ const Legend = ({ {[...Array(max+1-min).keys()].map(i => i+min).map(i => 0} onMouseOver={() => onSegmentFocus(i)} /> diff --git a/crabfit-frontend/src/components/Legend/Legend.styles.js b/crabfit-frontend/src/components/Legend/Legend.styles.js index c52040e..e739d92 100644 --- a/crabfit-frontend/src/components/Legend/Legend.styles.js +++ b/crabfit-frontend/src/components/Legend/Legend.styles.js @@ -43,10 +43,10 @@ export const Grade = styled('div')` ${props => props.$highlight && ` background-image: repeating-linear-gradient( 45deg, - var(--primary), - var(--primary) 4.5px, - var(--shadow) 4.5px, - var(--shadow) 9px + transparent, + transparent 4.5px, + rgba(0,0,0,.5) 4.5px, + rgba(0,0,0,.5) 9px ); `} ` diff --git a/crabfit-frontend/src/components/Settings/Settings.jsx b/crabfit-frontend/src/components/Settings/Settings.jsx index 0370524..0117777 100644 --- a/crabfit-frontend/src/components/Settings/Settings.jsx +++ b/crabfit-frontend/src/components/Settings/Settings.jsx @@ -3,6 +3,7 @@ import { useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import dayjs from 'dayjs' import { Settings as SettingsIcon } from 'lucide-react' +import { maps } from 'hue-map' import { ToggleField, SelectField } from '/src/components' @@ -125,6 +126,24 @@ const Settings = () => { onChange={value => store.setTheme(value)} /> + [ + palette, + palette.split('-') + .map(w => w[0].toLocaleUpperCase() + w.substring(1).toLocaleLowerCase()) + .join(' '), + ])), + }} + small + value={store.colormap} + onChange={event => store.setColormap(event.target.value)} + /> + set({ weekStart }), setTimeFormat: timeFormat => set({ timeFormat }), setTheme: theme => set({ theme }), setHighlight: highlight => set({ highlight }), + setColormap: colormap => set({ colormap }), }), { name: 'crabfit-settings' }, )) diff --git a/crabfit-frontend/yarn.lock b/crabfit-frontend/yarn.lock index ecea15c..86b20f5 100644 --- a/crabfit-frontend/yarn.lock +++ b/crabfit-frontend/yarn.lock @@ -2185,6 +2185,11 @@ html-parse-stringify@^3.0.1: dependencies: void-elements "3.1.0" +hue-map@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/hue-map/-/hue-map-0.1.1.tgz#d6b199f4121df56aaf1901222323545f0607a31d" + integrity sha512-aH3Maa39vHN+yTkWXdLcTN8M+ftGOX6NvGuMObl2puWxwoUZhm3gLlKA6SdpqJphF1RzQo0ntsm0Psg0uc7ouQ== + i18next-browser-languagedetector@^6.1.5: version "6.1.5" resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.5.tgz#ed8c9319a8d246995d8ec8fccb5bf5f4248d0fb1"