Stian Fredrik Aune
4 years ago
15 changed files with 362 additions and 57 deletions
-
4webui/package.json
-
34webui/src/App.tsx
-
10webui/src/actions/ColorPresets.ts
-
53webui/src/contexts/DataContext.tsx
-
2webui/src/helpers/kelvin.ts
-
3webui/src/helpers/misc.ts
-
81webui/src/helpers/rest.ts
-
5webui/src/index.tsx
-
28webui/src/models/Icons.ts
-
4webui/src/models/Shared.ts
-
24webui/src/pages/SettingsPage.tsx
-
54webui/src/primitives/Elements.tsx
-
8webui/src/primitives/Layout.sass
-
54webui/src/primitives/Layout.tsx
-
53webui/yarn.lock
@ -0,0 +1,10 @@ |
|||||
|
import {ColorPreset} from "../models/Colors"; |
||||
|
import {getRequest, postRequest} from "../helpers/rest"; |
||||
|
|
||||
|
export function listColorPresets(): Promise<ColorPreset[]> { |
||||
|
return getRequest("/color-presets"); |
||||
|
} |
||||
|
|
||||
|
export function addColorPreset(name: string, colorString: string) { |
||||
|
return postRequest("/color-presets", {name, colorString}); |
||||
|
} |
@ -1,5 +1,56 @@ |
|||||
import React from "react"; |
|
||||
|
import React, {createContext, useCallback, useEffect, useState} from "react"; |
||||
|
import {ColorPreset} from "../models/Colors"; |
||||
|
import {DataTarget} from "../models/Shared"; |
||||
|
import {unimplemented} from "../helpers/misc"; |
||||
|
import {listColorPresets} from "../actions/ColorPresets"; |
||||
|
|
||||
interface DataContextValue { |
interface DataContextValue { |
||||
|
colorPresets: ColorPreset[] |
||||
|
|
||||
|
ready(target?: DataTarget): boolean |
||||
|
|
||||
|
refresh(target?: DataTarget): void |
||||
} |
} |
||||
|
|
||||
|
const DataContext = createContext<DataContextValue>({ |
||||
|
colorPresets: [], |
||||
|
ready: unimplemented, |
||||
|
refresh: unimplemented, |
||||
|
}); |
||||
|
|
||||
|
export const DataContextProvider: React.FC = ({children}) => { |
||||
|
const [colorPresets, setColorPresets] = useState<ColorPreset[]>([]); |
||||
|
|
||||
|
const [readyMap, setReadyMap] = useState<{ [key: string]: boolean }>({ |
||||
|
[DataTarget.Default]: false, |
||||
|
[DataTarget.ColorPresets]: false, |
||||
|
}); |
||||
|
|
||||
|
const ready = useCallback((target: DataTarget = DataTarget.Default) => { |
||||
|
return readyMap[target] || false; |
||||
|
}, [readyMap]); |
||||
|
|
||||
|
const refresh = useCallback((target: DataTarget = DataTarget.Default) => { |
||||
|
const refreshing: Promise<any>[] = []; |
||||
|
if ([DataTarget.Default, DataTarget.ColorPresets].includes(target)) { |
||||
|
refreshing.push(listColorPresets().then(setColorPresets)); |
||||
|
} |
||||
|
|
||||
|
Promise.all(refreshing).then(() => setReadyMap(prev => ({...prev, [target]: true}))); |
||||
|
}, []); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
refresh(); |
||||
|
}, [refresh]); |
||||
|
|
||||
|
return ( |
||||
|
<DataContext.Provider value={{ |
||||
|
colorPresets, |
||||
|
ready, refresh, |
||||
|
}}> |
||||
|
{children} |
||||
|
</DataContext.Provider> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default DataContext; |
@ -0,0 +1,3 @@ |
|||||
|
export function unimplemented<T>(): T { |
||||
|
throw new Error("unimplemented"); |
||||
|
} |
@ -0,0 +1,81 @@ |
|||||
|
import Axios, {AxiosError, AxiosRequestConfig, AxiosResponse} from "axios"; |
||||
|
|
||||
|
interface Response<T> { |
||||
|
code: number |
||||
|
message: string |
||||
|
data: T |
||||
|
} |
||||
|
|
||||
|
export const authError = new Error("unauthenticated"); |
||||
|
|
||||
|
export async function getRequest<T>(url: string): Promise<T> { |
||||
|
const cors = url.includes("//"); |
||||
|
|
||||
|
return sendRequest<T>({ |
||||
|
method: "GET", |
||||
|
url: cors ? url : `/api${url}`, |
||||
|
responseType: "json", |
||||
|
}, cors); |
||||
|
} |
||||
|
|
||||
|
export function postRequest<T>(url: string, data: object = {}): Promise<T> { |
||||
|
return sendRequest<T>({ |
||||
|
method: "POST", |
||||
|
url: `/api${url}`, |
||||
|
responseType: "json", |
||||
|
data, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function putRequest<T>(url: string, data: object): Promise<T> { |
||||
|
return sendRequest<T>({ |
||||
|
method: "PUT", |
||||
|
url: `/api${url}`, |
||||
|
responseType: "json", |
||||
|
data, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function deleteRequest<T>(url: string): Promise<T> { |
||||
|
return sendRequest<T>({ |
||||
|
method: "DELETE", |
||||
|
url: `/api${url}`, |
||||
|
responseType: "json", |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async function sendRequest<T>(config: AxiosRequestConfig, cors: boolean = false): Promise<T> { |
||||
|
try { |
||||
|
const res = await Axios(config); |
||||
|
|
||||
|
return await handleResponse(res, cors); |
||||
|
} catch (e) { |
||||
|
if (e === authError) { |
||||
|
throw e; |
||||
|
} |
||||
|
|
||||
|
return await handleResponse((e as AxiosError).response as AxiosResponse); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleResponse<T>(response: AxiosResponse, object: boolean = false): Promise<T> { |
||||
|
if (object) { |
||||
|
return Promise.resolve(response.data); |
||||
|
} else { |
||||
|
const obj: Response<T> | undefined = response?.data; |
||||
|
|
||||
|
if (![200, 201].includes(response.status)) { |
||||
|
if (response.status === 403) { |
||||
|
return Promise.reject(authError); |
||||
|
} else { |
||||
|
return Promise.reject(obj?.message || "Ukjent feil"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!obj) { |
||||
|
return Promise.reject("Ingen data mottatt"); |
||||
|
} |
||||
|
|
||||
|
return Promise.resolve(obj.data); |
||||
|
} |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
import bridgeIcon from "@iconify-icons/mdi/bridge"; |
||||
|
import hexagonIcon from "@iconify-icons/mdi/hexagon"; |
||||
|
import lightBulbIcon from "@iconify-icons/mdi/lightbulb"; |
||||
|
import lightBulbGroupIcon from "@iconify-icons/mdi/lightbulb-group"; |
||||
|
import squareIcon from "@iconify-icons/mdi/square-rounded"; |
||||
|
|
||||
|
// To add a new icon, follow the instructions on this page:
|
||||
|
// https://iconify.design/icon-sets/mdi/
|
||||
|
|
||||
|
export enum LuciferIcon { |
||||
|
Bridge = "Bridge", |
||||
|
Bulb = "Bulb", |
||||
|
BulbGroup = "BulbGroup", |
||||
|
Hexagon = "Hexagon", |
||||
|
Square = "Square", |
||||
|
} |
||||
|
|
||||
|
const iconMap = { |
||||
|
[LuciferIcon.Bridge]: bridgeIcon, |
||||
|
[LuciferIcon.Bulb]: lightBulbIcon, |
||||
|
[LuciferIcon.BulbGroup]: lightBulbGroupIcon, |
||||
|
[LuciferIcon.Hexagon]: hexagonIcon, |
||||
|
[LuciferIcon.Square]: squareIcon, |
||||
|
} |
||||
|
|
||||
|
export function toIconify(icon: LuciferIcon): object { |
||||
|
return iconMap[icon]; |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
export enum DataTarget { |
||||
|
Default = "", |
||||
|
ColorPresets = "ColorPresets", |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
import React, {useContext, useEffect} from "react"; |
||||
|
import {Page} from "../primitives/Layout"; |
||||
|
import DataContext from "../contexts/DataContext"; |
||||
|
import {IconElement} from "../primitives/Elements"; |
||||
|
import {addColorPreset} from "../actions/ColorPresets"; |
||||
|
|
||||
|
const SettingsPage: React.FC = () => { |
||||
|
const {colorPresets, refresh} = useContext(DataContext); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
}, []); |
||||
|
|
||||
|
return ( |
||||
|
<Page title="Oppsett"> |
||||
|
<h1>Definerte farger</h1> |
||||
|
{colorPresets.map(cp => ( |
||||
|
<IconElement key={cp.id} {...cp} caption={cp.name}/> |
||||
|
))} |
||||
|
<button>Add</button> |
||||
|
</Page> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default SettingsPage; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue