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
-
44webui/src/primitives/Elements.tsx
-
8webui/src/primitives/Layout.sass
-
40webui/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 { |
|||
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