diff --git a/app/api/presets.go b/app/api/presets.go index 6682d21..2b443da 100644 --- a/app/api/presets.go +++ b/app/api/presets.go @@ -80,4 +80,18 @@ func ColorPresets(r gin.IRoutes) { return preset, nil })) + + r.DELETE("/:id", handler(func(c *gin.Context) (interface{}, error) { + preset, err := config.ColorPresetRepository().Find(ctxOf(c), intParam(c, "id")) + if err != nil { + return nil, err + } + + err = config.ColorPresetRepository().Delete(ctxOf(c), &preset) + if err != nil { + return nil, err + } + + return preset, nil + })) } diff --git a/webui/src/actions/ColorPresets.ts b/webui/src/actions/ColorPresets.ts index 71cec1d..e5c3403 100644 --- a/webui/src/actions/ColorPresets.ts +++ b/webui/src/actions/ColorPresets.ts @@ -1,5 +1,5 @@ import {ColorPreset} from "../models/Colors"; -import {getRequest, postRequest} from "../helpers/rest"; +import {deleteRequest, getRequest, postRequest, putRequest} from "../helpers/rest"; export function listColorPresets(): Promise { return getRequest("/color-presets"); @@ -8,3 +8,11 @@ export function listColorPresets(): Promise { export function addColorPreset(name: string, colorString: string) { return postRequest("/color-presets", {name, colorString}); } + +export function editColorPreset(id: number, name: string, colorString: string) { + return putRequest(`/color-presets/${id}`, {name, colorString}); +} + +export function deleteColorPreset(id: number) { + return deleteRequest(`/color-presets/${id}`); +} diff --git a/webui/src/contexts/DialogContext.tsx b/webui/src/contexts/DialogContext.tsx index c7e3d2c..71ac3ad 100644 --- a/webui/src/contexts/DialogContext.tsx +++ b/webui/src/contexts/DialogContext.tsx @@ -1,22 +1,29 @@ -import React, {createContext, useState} from "react"; +import React, {createContext, useCallback, useState} from "react"; import {DialogConfig, DialogType} from "../models/Dialog"; import {unimplemented} from "../helpers/misc"; +import ColorDialog from "../dialogs/ColorDialog"; interface DialogContextValue { dialog: DialogConfig, setDialog: (dialog: DialogConfig) => void, + clearDialog: () => void, } const DialogContext = createContext({ dialog: {type: DialogType.None}, setDialog: unimplemented, + clearDialog: unimplemented, }); export const DialogContextProvider: React.FC = ({children}) => { const [dialog, setDialog] = useState({type: DialogType.None}); + const clearDialog = useCallback(() => { + setDialog({type: DialogType.None}); + }, []); + return ( - + {children} ); @@ -27,7 +34,7 @@ export function makeDialog(dialog: DialogConfig) { case DialogType.None: return undefined; case DialogType.AddColor: - return undefined; + return ; } } diff --git a/webui/src/dialogs/ColorDialog.tsx b/webui/src/dialogs/ColorDialog.tsx index 80c708c..9b0f704 100644 --- a/webui/src/dialogs/ColorDialog.tsx +++ b/webui/src/dialogs/ColorDialog.tsx @@ -1,23 +1,71 @@ -import React, {useCallback, useContext, useState} from "react"; +import React, {useCallback, useContext, useEffect, useState} from "react"; import {ColorPreset} from "../models/Colors"; import DialogContext from "../contexts/DialogContext"; import {DialogType} from "../models/Dialog"; +import {Dialog} from "../primitives/Layout"; +import {LuciferIcon} from "../models/Icons"; +import {HSColorPicker, Input, KelvinColorPicker} from "../primitives/Forms"; interface ColorDialogProps { data?: ColorPreset } +enum ColorType { + HueSat, + Kelvin, + HueSatKelvin, +} + +const COLOR_TYPE_OPTIONS = [ + {value: ColorType.HueSat, name: "Bare Nyanse (H) og metning (S)"}, + {value: ColorType.Kelvin, name: "Bare temperatur (K)"}, + {value: ColorType.HueSatKelvin, name: "Nyanse (H), metning (K) og kelvin (S)"}, +]; + export default function ColorDialog({data}: ColorDialogProps) { - const {setDialog} = useContext(DialogContext); + const {setDialog, clearDialog} = useContext(DialogContext); const [name, setName] = useState(data?.name || ""); const [hue, setHue] = useState(data?.value?.h || 0); const [sat, setSat] = useState(data?.value?.s || 0); const [kelvin, setKelvin] = useState(data?.value?.kelvin || 0); - const onClose = useCallback(() => { + const [colorType, setColorType] = useState(COLOR_TYPE_OPTIONS[0].value); + const [isInit, setIsInit] = useState(false); + + const onSave = useCallback(() => { setDialog({type: DialogType.None}); }, [setDialog]); + useEffect(() => { + if (!isInit && data) { + const v = data.value; + + if (v.kelvin !== undefined && v.kelvin > 0) { + setColorType((v.h || v.s) ? ColorType.HueSatKelvin : ColorType.Kelvin); + } + } + + if (!isInit) { + setIsInit(true); + } + }, [isInit, data, colorType]); + return ( + + + {colorType !== ColorType.Kelvin && ( + { + setHue(h); + setSat(s); + }}/> + )} + {colorType !== ColorType.HueSat && ( + + )} + + ); } diff --git a/webui/src/pages/SettingsPage.tsx b/webui/src/pages/SettingsPage.tsx index 2713c19..d609592 100644 --- a/webui/src/pages/SettingsPage.tsx +++ b/webui/src/pages/SettingsPage.tsx @@ -2,8 +2,11 @@ import React, {useContext, useEffect} from "react"; import {Page} from "../primitives/Layout"; import DataContext from "../contexts/DataContext"; import {IconElement} from "../primitives/Elements"; +import DialogContext from "../contexts/DialogContext"; +import {DialogType} from "../models/Dialog"; const SettingsPage: React.FC = () => { + const {setDialog} = useContext(DialogContext); const {colorPresets} = useContext(DataContext); useEffect(() => { @@ -15,7 +18,7 @@ const SettingsPage: React.FC = () => { {colorPresets.map(cp => ( ))} - + ); }; diff --git a/webui/src/primitives/Forms.sass b/webui/src/primitives/Forms.sass new file mode 100644 index 0000000..148f8f1 --- /dev/null +++ b/webui/src/primitives/Forms.sass @@ -0,0 +1,20 @@ +@import "../res/colors" + +.FormLine + padding: 0.5em 1ch + width: 100% + + &:first-of-type + margin-top: 0 + + input, select + display: block + width: 100% + font: inherit + background-color: $color-background-elevated + color: $color-foreground + border: none + padding: 0.25em 0.5ch + + &:focus, &:active + outline: 1px solid $color-foreground-elevated diff --git a/webui/src/primitives/Forms.tsx b/webui/src/primitives/Forms.tsx index 89881a7..5c6db78 100644 --- a/webui/src/primitives/Forms.tsx +++ b/webui/src/primitives/Forms.tsx @@ -1,21 +1,66 @@ -import React, {useLayoutEffect, useMemo} from 'react'; -// @ts-ignore +import React, {PropsWithChildren, useCallback, useLayoutEffect, useMemo} from 'react'; import iro from "@jaames/iro"; +import "./Forms.sass"; + +interface FormLineProps { + label: string + id?: string +} + +function FormLine({label, id, children}: PropsWithChildren) { + return ( +
+ + {children} +
+ ); +} + +interface InputProps { + label: string + value: string + onChange?: (s: string) => void, + onChangeNumeric?: (n: number) => void + numeric?: boolean +} + +export function Input({label, numeric, onChange, onChangeNumeric, value}: InputProps) { + const id = useMemo(() => `input-${randomId()}`, []); + + const actualOnChange = useCallback((newValue: string) => { + if (onChangeNumeric) { + onChangeNumeric(parseInt(newValue)); + } else if (onChange) { + onChange(newValue); + } + }, [onChange, onChangeNumeric]); + + return ( + + actualOnChange(i.target.value)} + /> + + ) +} + interface ColorPickerProps { + label?: string h: number s: number - onChange: (h: number, v: number) => void + onChange: (h: number, s: number) => void } const randomId = () => Math.floor(Math.random() * 100000); -export const HSColorPicker: React.FC = ({h, s, onChange}) => { - const random = useMemo(() => `color-picker-${randomId()}`, []); +export const HSColorPicker: React.FC = ({h, s, onChange, label}) => { + const myId = useMemo(() => `color-picker-${randomId()}`, []); useLayoutEffect(() => { // @ts-ignore - const colorPicker = new iro.ColorPicker(`#${random}`, { + const colorPicker = new iro.ColorPicker(`#${myId}`, { color: {h, s: s * 100, v: 255}, layout: [ { @@ -30,14 +75,62 @@ export const HSColorPicker: React.FC = ({h, s, onChange}) => { }); return () => { - const elem = document.getElementById(`color-picker-${random}`); + const elem = document.getElementById(myId); if (elem === null) { return; } elem.innerHTML = ""; }; - }, [h, s, onChange, random]); + }, [h, s, onChange, myId]); - return
; + return ( + +
+ + ); }; + +interface KelvinColorPickerProps { + label?: string + kelvin: number + onChange: (kelvin: number) => void +} + +export const KelvinColorPicker: React.FC = ({label, kelvin, onChange}) => { + const myId = useMemo(() => `color-picker-${randomId()}`, []); + + useLayoutEffect(() => { + // @ts-ignore + const colorPicker = new iro.ColorPicker(`#${myId}`, { + color: {kelvin}, + layout: [ + { + component: iro.ui.Slider, + options: { + sliderType: "kelvin", + } + } + ], + }); + + colorPicker.on("input:end", (color: iro.Color) => { + onChange(Math.floor(color.kelvin)); + }); + + return () => { + const elem = document.getElementById(myId); + if (elem === null) { + return; + } + + elem.innerHTML = ""; + }; + }, [kelvin, onChange, myId]); + + return ( + +
+ + ); +} diff --git a/webui/src/primitives/Layout.tsx b/webui/src/primitives/Layout.tsx index de54564..f356aaf 100644 --- a/webui/src/primitives/Layout.tsx +++ b/webui/src/primitives/Layout.tsx @@ -77,8 +77,6 @@ interface DialogProps { } export function Dialog({title, buttons, children}: PropsWithChildren) { - - return ( {buttons?.map(b => ( -