wip: colors
This commit is contained in:
@@ -80,4 +80,18 @@ func ColorPresets(r gin.IRoutes) {
|
|||||||
|
|
||||||
return preset, nil
|
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
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {ColorPreset} from "../models/Colors";
|
import {ColorPreset} from "../models/Colors";
|
||||||
import {getRequest, postRequest} from "../helpers/rest";
|
import {deleteRequest, getRequest, postRequest, putRequest} from "../helpers/rest";
|
||||||
|
|
||||||
export function listColorPresets(): Promise<ColorPreset[]> {
|
export function listColorPresets(): Promise<ColorPreset[]> {
|
||||||
return getRequest("/color-presets");
|
return getRequest("/color-presets");
|
||||||
@@ -8,3 +8,11 @@ export function listColorPresets(): Promise<ColorPreset[]> {
|
|||||||
export function addColorPreset(name: string, colorString: string) {
|
export function addColorPreset(name: string, colorString: string) {
|
||||||
return postRequest("/color-presets", {name, colorString});
|
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}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
import React, {createContext, useState} from "react";
|
import React, {createContext, useCallback, useState} from "react";
|
||||||
import {DialogConfig, DialogType} from "../models/Dialog";
|
import {DialogConfig, DialogType} from "../models/Dialog";
|
||||||
import {unimplemented} from "../helpers/misc";
|
import {unimplemented} from "../helpers/misc";
|
||||||
|
import ColorDialog from "../dialogs/ColorDialog";
|
||||||
|
|
||||||
interface DialogContextValue {
|
interface DialogContextValue {
|
||||||
dialog: DialogConfig,
|
dialog: DialogConfig,
|
||||||
setDialog: (dialog: DialogConfig) => void,
|
setDialog: (dialog: DialogConfig) => void,
|
||||||
|
clearDialog: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DialogContext = createContext<DialogContextValue>({
|
const DialogContext = createContext<DialogContextValue>({
|
||||||
dialog: {type: DialogType.None},
|
dialog: {type: DialogType.None},
|
||||||
setDialog: unimplemented,
|
setDialog: unimplemented,
|
||||||
|
clearDialog: unimplemented,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DialogContextProvider: React.FC = ({children}) => {
|
export const DialogContextProvider: React.FC = ({children}) => {
|
||||||
const [dialog, setDialog] = useState<DialogConfig>({type: DialogType.None});
|
const [dialog, setDialog] = useState<DialogConfig>({type: DialogType.None});
|
||||||
|
|
||||||
|
const clearDialog = useCallback(() => {
|
||||||
|
setDialog({type: DialogType.None});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContext.Provider value={{dialog, setDialog}}>
|
<DialogContext.Provider value={{dialog, setDialog, clearDialog}}>
|
||||||
{children}
|
{children}
|
||||||
</DialogContext.Provider>
|
</DialogContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -27,7 +34,7 @@ export function makeDialog(dialog: DialogConfig) {
|
|||||||
case DialogType.None:
|
case DialogType.None:
|
||||||
return undefined;
|
return undefined;
|
||||||
case DialogType.AddColor:
|
case DialogType.AddColor:
|
||||||
return undefined;
|
return <ColorDialog/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import React, {useCallback, useContext, useState} from "react";
|
|||||||
import {ColorPreset} from "../models/Colors";
|
import {ColorPreset} from "../models/Colors";
|
||||||
import DialogContext from "../contexts/DialogContext";
|
import DialogContext from "../contexts/DialogContext";
|
||||||
import {DialogType} from "../models/Dialog";
|
import {DialogType} from "../models/Dialog";
|
||||||
|
import {Dialog} from "../primitives/Layout";
|
||||||
|
import {LuciferIcon} from "../models/Icons";
|
||||||
|
import {HSColorPicker, Input, KelvinColorPicker} from "../primitives/Forms";
|
||||||
|
|
||||||
interface ColorDialogProps {
|
interface ColorDialogProps {
|
||||||
data?: ColorPreset
|
data?: ColorPreset
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ColorDialog({data}: ColorDialogProps) {
|
export default function ColorDialog({data}: ColorDialogProps) {
|
||||||
const {setDialog} = useContext(DialogContext);
|
const {setDialog, clearDialog} = useContext(DialogContext);
|
||||||
|
|
||||||
const [name, setName] = useState(data?.name || "");
|
const [name, setName] = useState(data?.name || "");
|
||||||
const [hue, setHue] = useState(data?.value?.h || 0);
|
const [hue, setHue] = useState(data?.value?.h || 0);
|
||||||
@@ -19,5 +22,17 @@ export default function ColorDialog({data}: ColorDialogProps) {
|
|||||||
setDialog({type: DialogType.None});
|
setDialog({type: DialogType.None});
|
||||||
}, [setDialog]);
|
}, [setDialog]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog title={data ? "Endre farge" : "Ny farge"} buttons={[
|
||||||
|
{icon: LuciferIcon.Check, text: "Lagre"},
|
||||||
|
{icon: LuciferIcon.Cross, text: "Avbryt", onClick: clearDialog},
|
||||||
|
]}>
|
||||||
|
<Input label="Navn" value={name} onChange={setName}/>
|
||||||
|
<HSColorPicker h={hue} s={sat} onChange={(h, s) => {
|
||||||
|
setHue(h);
|
||||||
|
setSat(s);
|
||||||
|
}}/>
|
||||||
|
<KelvinColorPicker kelvin={kelvin} onChange={setKelvin}/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import React, {useContext, useEffect} from "react";
|
|||||||
import {Page} from "../primitives/Layout";
|
import {Page} from "../primitives/Layout";
|
||||||
import DataContext from "../contexts/DataContext";
|
import DataContext from "../contexts/DataContext";
|
||||||
import {IconElement} from "../primitives/Elements";
|
import {IconElement} from "../primitives/Elements";
|
||||||
|
import DialogContext from "../contexts/DialogContext";
|
||||||
|
import {DialogType} from "../models/Dialog";
|
||||||
|
|
||||||
const SettingsPage: React.FC = () => {
|
const SettingsPage: React.FC = () => {
|
||||||
|
const {setDialog} = useContext(DialogContext);
|
||||||
const {colorPresets} = useContext(DataContext);
|
const {colorPresets} = useContext(DataContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -15,7 +18,7 @@ const SettingsPage: React.FC = () => {
|
|||||||
{colorPresets.map(cp => (
|
{colorPresets.map(cp => (
|
||||||
<IconElement key={cp.id} color={cp.value} caption={cp.name}/>
|
<IconElement key={cp.id} color={cp.value} caption={cp.name}/>
|
||||||
))}
|
))}
|
||||||
<button>Add</button>
|
<button onClick={() => setDialog({type: DialogType.AddColor})}>Add</button>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
@import "../res/colors"
|
||||||
|
|
||||||
|
.FormLine
|
||||||
|
margin: 0.5em 1ch
|
||||||
|
|
||||||
|
&:first-of-type
|
||||||
|
margin-top: 0
|
||||||
|
|
||||||
|
input
|
||||||
|
display: block
|
||||||
@@ -1,21 +1,66 @@
|
|||||||
import React, {useLayoutEffect, useMemo} from 'react';
|
import React, {PropsWithChildren, useCallback, useLayoutEffect, useMemo} from 'react';
|
||||||
// @ts-ignore
|
|
||||||
import iro from "@jaames/iro";
|
import iro from "@jaames/iro";
|
||||||
|
|
||||||
|
import "./Forms.sass";
|
||||||
|
|
||||||
|
interface FormLineProps {
|
||||||
|
label: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormLine({label, id, children}: PropsWithChildren<FormLineProps>) {
|
||||||
|
return (
|
||||||
|
<div className="FormLine">
|
||||||
|
<label htmlFor={id}>{label}:</label>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<FormLine label={label} id={id}>
|
||||||
|
<input id={id} value={value}
|
||||||
|
type={numeric ? "number" : "text"}
|
||||||
|
onChange={i => actualOnChange(i.target.value)}
|
||||||
|
/>
|
||||||
|
</FormLine>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface ColorPickerProps {
|
interface ColorPickerProps {
|
||||||
|
label?: string
|
||||||
h: number
|
h: number
|
||||||
s: number
|
s: number
|
||||||
onChange: (h: number, v: number) => void
|
onChange: (h: number, s: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomId = () => Math.floor(Math.random() * 100000);
|
const randomId = () => Math.floor(Math.random() * 100000);
|
||||||
|
|
||||||
export const HSColorPicker: React.FC<ColorPickerProps> = ({h, s, onChange}) => {
|
export const HSColorPicker: React.FC<ColorPickerProps> = ({h, s, onChange, label}) => {
|
||||||
const random = useMemo(() => `color-picker-${randomId()}`, []);
|
const myId = useMemo(() => `color-picker-${randomId()}`, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const colorPicker = new iro.ColorPicker(`#${random}`, {
|
const colorPicker = new iro.ColorPicker(`#${myId}`, {
|
||||||
color: {h, s: s * 100, v: 255},
|
color: {h, s: s * 100, v: 255},
|
||||||
layout: [
|
layout: [
|
||||||
{
|
{
|
||||||
@@ -30,14 +75,62 @@ export const HSColorPicker: React.FC<ColorPickerProps> = ({h, s, onChange}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const elem = document.getElementById(`color-picker-${random}`);
|
const elem = document.getElementById(myId);
|
||||||
if (elem === null) {
|
if (elem === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.innerHTML = "";
|
elem.innerHTML = "";
|
||||||
};
|
};
|
||||||
}, [h, s, onChange, random]);
|
}, [h, s, onChange, myId]);
|
||||||
|
|
||||||
return <div id={random} style={{margin: "0 auto"}}/>;
|
return (
|
||||||
|
<FormLine label={label || "HS-farge"}>
|
||||||
|
<div id={myId} style={{margin: "0 auto"}}/>
|
||||||
|
</FormLine>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface KelvinColorPickerProps {
|
||||||
|
label?: string
|
||||||
|
kelvin: number
|
||||||
|
onChange: (kelvin: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KelvinColorPicker: React.FC<KelvinColorPickerProps> = ({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(color.kelvin);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const elem = document.getElementById(myId);
|
||||||
|
if (elem === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.innerHTML = "";
|
||||||
|
};
|
||||||
|
}, [kelvin, onChange, myId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormLine label={label || "KV-farge"}>
|
||||||
|
<div id={myId} style={{margin: "0 auto"}}/>
|
||||||
|
</FormLine>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,8 +77,6 @@ interface DialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Dialog({title, buttons, children}: PropsWithChildren<DialogProps>) {
|
export function Dialog({title, buttons, children}: PropsWithChildren<DialogProps>) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactModal isOpen={true}
|
<ReactModal isOpen={true}
|
||||||
className="Dialog-main"
|
className="Dialog-main"
|
||||||
@@ -91,7 +89,7 @@ export function Dialog({title, buttons, children}: PropsWithChildren<DialogProps
|
|||||||
{children}
|
{children}
|
||||||
<div className="Dialog-footer">
|
<div className="Dialog-footer">
|
||||||
{buttons?.map(b => (
|
{buttons?.map(b => (
|
||||||
<button key={b.text}>
|
<button key={b.text} onClick={b.onClick}>
|
||||||
<IconBlock icon={b.icon}/>
|
<IconBlock icon={b.icon}/>
|
||||||
{" "}
|
{" "}
|
||||||
{b.text}
|
{b.text}
|
||||||
|
|||||||
Reference in New Issue
Block a user