Source: components/pattern_settings/index.js

import Patterns from "../../components/patterns";
import NumericSlider from "../../components/numeric_slider";
import {useState, useEffect} from "preact/hooks";
import {
	getPattern,
	updatePattern} from "../../utils/api";
import {useConnectivity} from "../../context/online_context";
import useInterval from "../../utils/use_interval";
import { LabelSpinner } from '../../components/spinner';
import RANGE_CONSTANTS from '../../utils/constants';
import style from './style.css';
import MultiRangeSliderWrapper from '../../components/multi_range_slider';
import ConfigDropDown from "../config_drop_down";

/**
 * @brief An object meant to hold and display settings for a specific pattern
 * @param num 	The ID of the pattern to display
 * @param patterns	A list of patterns and their IDs
 */
const PatternSettings = ({num, patterns}) => {

	// Checks if the web app is connected to the device.
	const { isConnected } = useConnectivity();

	// Flag representing if initial data has been obtained from
	// the device.
	const [loading, setLoading] = useState(true);

	// Flag that tells the object to update the NanoLux device with new data.
	const [updated, setUpdated] = useState(false);

	// Pattern-level data structure
	const [data, setData] = useState({
		idx: 0,
		hue_max: 255,
		hue_min: 0,
		brightness: 255,
		smoothing: 0,
		postprocess: 0,
		config: 0
	});

	/**
	 * @brief Manages initial querying of the data from the NanoLux device.
	 * 		  Sets the loading flag to false when done.
	 */
	useEffect(() => {
        if (isConnected) {
            getPattern(num)
			.then(data => {setData(data)})
			.then(setLoading(false));
        }  
    }, [isConnected])

	/**
	 * @brief Updates the pattern on the Nanolux device 
	 * if it is connected and has modified data,
	 * then flags the data structure.
	 */
	useInterval(() => {
        if (isConnected && updated) {
            updatePattern(num, data);
			setUpdated(false);
        }
    }, 100);

	/**
	 * @brief Updates a parameter in the pattern data structure with a new value.
	 * @param ref The string reference to update in the data structure
	 * @param value The new value to update the data structure with
	 */
	const update = (ref, value) => {
		if(!loading){		
			setData((oldData) => {
				let newData = Object.assign({}, oldData);
				newData[ref] = value;
				return newData;
			})
		}
		
		setUpdated(true);
	}

	/**
	 * @brief Generates the Subpattern UI element itself. If initial data is not
	 * loaded, show a spinner instead.
	 */
	return (
		(!loading ? 
		<div>
			<br/>
			<Patterns
				initialID={data.idx}
				structure_ref={"idx"}
				update={update}
				patterns={patterns}
			/>
			<br/>
			<ConfigDropDown
				patternIdx={data.idx}
				structureRef={"config"}
				update={update}
				initial={data.config}
			/>
			<br/>
			<NumericSlider
				className={style.settings_control}
				label="Brightness"
				min={RANGE_CONSTANTS.BRIGHTNESS_MIN}
				max={RANGE_CONSTANTS.BRIGHTNESS_MAX}
				initial={data.brightness}
				structure_ref="brightness"
				update={update}
			/>
			<br/>
			<NumericSlider
				className={style.settings_control}
				label="Smoothing"
				min={RANGE_CONSTANTS.SMOOTHING_MIN}
				max={RANGE_CONSTANTS.SMOOTHING_MAX}
				initial={data.smoothing}
				structure_ref="smoothing"
				update={update}
			/>
			<div className={style.settings_control}>
                <label for="reverse">Reverse</label>
				<input 
					type="checkbox" 
					id="reverse" 
					name="reverse" 
					checked={(data.postprocess == 1) || (data.postprocess == 3)}
					onChange={() => {
						switch (data.postprocess) {
							case 0:
								update("postprocess", 1);
								break;
							
							case 1:
								update("postprocess", 0);
								break;
							
							case 2:
								update("postprocess", 3);
								break;
						
							default:
								update("postprocess", 2);
								break;
						}
					}}
				/>
				<label for="mirror">Mirror</label>
				<input 
					type="checkbox" 
					id="mirror" 
					name="mirror" 
					checked={(data.postprocess == 2) || (data.postprocess == 3)}
					onChange={() => {
						switch (data.postprocess) {
							case 0:
								update("postprocess", 2);
								break;
							
							case 1:
								update("postprocess", 3);
								break;
							
							case 2:
								update("postprocess", 0);
								break;
						
							default:
								update("postprocess", 1);
								break;
						}
					}}
				/>
            </div>
			<MultiRangeSliderWrapper
				min={0}
				max={255}
				selectedLow={data.hue_min}
				selectedHigh={data.hue_max}
				minRef={"hue_min"}
				maxRef={"hue_max"}
				update={update}
			/>
		</div> : <LabelSpinner></LabelSpinner>
		)
	);
}

export default PatternSettings;