Browse Source

dialogs sorta work now

pull/1/head
Stian Fredrik Aune 3 years ago
parent
commit
22c80a90a1
  1. 5
      webui/package.json
  2. 49
      webui/src/App.tsx
  3. 34
      webui/src/contexts/DialogContext.tsx
  4. 9
      webui/src/models/Dialog.ts
  5. 23
      webui/src/models/Icons.ts
  6. 3
      webui/src/pages/SettingsPage.tsx
  7. 20
      webui/src/primitives/Elements.tsx
  8. 69
      webui/src/primitives/Layout.sass
  9. 41
      webui/src/primitives/Layout.tsx
  10. 3
      webui/src/res/colors.sass
  11. 19
      webui/yarn.lock

5
webui/package.json

@ -19,7 +19,7 @@
"node-sass": "4.14.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-modal": "^3.13.1",
"react-modal": "^3.14.2",
"react-scripts": "4.0.3",
"react-semantic-ui-range": "^0.7.1",
"typescript": "^4.1.2",
@ -50,6 +50,7 @@
]
},
"devDependencies": {
"@types/hookrouter": "^2.2.5"
"@types/hookrouter": "^2.2.5",
"@types/react-modal": "^3.12.0"
}
}

49
webui/src/App.tsx

@ -1,16 +1,24 @@
import React from 'react';
import React, {useContext, useLayoutEffect} from 'react';
import {HookRouter, navigate, usePath, useRoutes} from "hookrouter";
import {Tabs} from "./primitives/Layout";
import {HSColorPicker} from "./primitives/Forms";
import {Dialog, Page, Tabs} from "./primitives/Layout";
import {IconElement} from "./primitives/Elements";
import SettingsPage from "./pages/SettingsPage";
import {LuciferIcon} from "./models/Icons";
import ReactModal from "react-modal";
import DialogContext, {DialogContextProvider, makeDialog} from "./contexts/DialogContext";
const routeObj: HookRouter.RouteObject = {
"/": () => (
<div>
<HSColorPicker h={30} s={1} onChange={() => void (0)}/>
</div>
<Page>
<Dialog title="Testdialog" buttons={[
{icon: LuciferIcon.Check, text: "Lagre"},
{icon: LuciferIcon.Cross, text: "Avbryt"},
]}>
Test
</Dialog>
<h1>&nbsp;</h1>
<h1>Enheter</h1>
</Page>
),
"/devices": () => (
<div>
@ -19,12 +27,13 @@ const routeObj: HookRouter.RouteObject = {
<IconElement icon={LuciferIcon.Bulb} caption="Kelvin 2k" color={{kelvin: 2000}}/>
<IconElement icon={LuciferIcon.BulbGroup} caption="Kelvin 3k" color={{kelvin: 3000}}/>
<IconElement icon={LuciferIcon.Bridge} caption="Kelvin 4k" color={{kelvin: 4000}}/>
<IconElement caption="Kelvin 5k" color={{kelvin: 5000}}/>
<IconElement caption="Kelvin 6k" color={{kelvin: 6000}}/>
<IconElement caption="Kelvin 7k" color={{kelvin: 7000}}/>
<IconElement caption="Kelvin 8k" color={{kelvin: 8000}}/>
<IconElement caption="Kelvin 9k" color={{kelvin: 9000}}/>
<IconElement caption="Kelvin 10k" color={{kelvin: 10000}}/>
<IconElement icon={LuciferIcon.Cog} caption="Kelvin 5k" color={{kelvin: 5000}}/>
<IconElement icon={LuciferIcon.Switch} caption="Kelvin 6k" color={{kelvin: 6000}}/>
<IconElement icon={LuciferIcon.Triangle} caption="Kelvin 7k" color={{kelvin: 7000}}/>
<IconElement icon={LuciferIcon.HexagonGroup} caption="Kelvin 8k" color={{kelvin: 8000}}/>
<IconElement icon={LuciferIcon.Router} caption="Kelvin 9k" color={{kelvin: 9000}}/>
<IconElement icon={LuciferIcon.Check} caption="Kelvin 10k" color={{kelvin: 10000}}/>
<IconElement icon={LuciferIcon.Cross} caption="Kelvin 10k" color={{kelvin: 10000}}/>
<IconElement caption="Mer" color="hs-gradient"/>
<IconElement caption="Mer" color="k-gradient"/>
</div>
@ -37,8 +46,21 @@ const routeList = ["/", "/devices", "/settings"];
const tabNames = ["Lucifer", "Enheter", "Oppsett"];
function App() {
return (
<DialogContextProvider>
<AppInContext/>
</DialogContextProvider>
);
}
function AppInContext() {
const route = useRoutes(routeObj);
const path = usePath();
const {dialog} = useContext(DialogContext);
useLayoutEffect(() => {
ReactModal.setAppElement("#root");
}, []);
return (
<div className="App">
@ -47,7 +69,8 @@ function App() {
onChange={i => navigate(routeList[i])}
large boldIndex={0}
/>
{route || <div>B</div>}
{route || <Page>404</Page>}
{makeDialog(dialog)}
</div>
);
}

34
webui/src/contexts/DialogContext.tsx

@ -0,0 +1,34 @@
import React, {createContext, useState} from "react";
import {DialogConfig, DialogType} from "../models/Dialog";
import {unimplemented} from "../helpers/misc";
interface DialogContextValue {
dialog: DialogConfig,
setDialog: (dialog: DialogConfig) => void,
}
const DialogContext = createContext<DialogContextValue>({
dialog: {type: DialogType.None},
setDialog: unimplemented,
});
export const DialogContextProvider: React.FC = ({children}) => {
const [dialog, setDialog] = useState<DialogConfig>({type: DialogType.None});
return (
<DialogContext.Provider value={{dialog, setDialog}}>
{children}
</DialogContext.Provider>
);
};
export function makeDialog(dialog: DialogConfig) {
switch (dialog.type) {
case DialogType.None:
return undefined;
case DialogType.AddColor:
return undefined;
}
}
export default DialogContext;

9
webui/src/models/Dialog.ts

@ -0,0 +1,9 @@
export enum DialogType {
None,
AddColor,
}
export type DialogConfig =
| { type: DialogType.None }
| { type: DialogType.AddColor }
;

23
webui/src/models/Icons.ts

@ -1,26 +1,49 @@
import bridgeIcon from "@iconify-icons/mdi/bridge";
import checkIcon from "@iconify-icons/mdi/check";
import crossIcon from "@iconify-icons/mdi/window-close";
import cogIcon from "@iconify-icons/mdi/cog";
import hexagonIcon from "@iconify-icons/mdi/hexagon";
import hexagonGroupIcon from "@iconify-icons/mdi/hexagon-multiple";
import lightBulbIcon from "@iconify-icons/mdi/lightbulb";
import lightBulbGroupIcon from "@iconify-icons/mdi/lightbulb-group";
import lightSwitchIcon from '@iconify-icons/mdi/light-switch';
import routerIcon from "@iconify-icons/mdi/router-wireless";
import squareIcon from "@iconify-icons/mdi/square-rounded";
import triangleIcon from "@iconify-icons/mdi/triangle";
// To add a new icon, follow the instructions on this page:
// https://iconify.design/icon-sets/mdi/
export enum LuciferIcon {
Bridge = "Bridge",
Check = "Check",
Cog = "Cog",
Cross = "Cross",
Bulb = "Bulb",
BulbGroup = "BulbGroup",
Hexagon = "Hexagon",
HexagonGroup = "HexagonGroup",
Router = "Router",
Square = "Square",
Switch = "Switch",
Triangle = "Triangle",
}
const iconMap = {
[LuciferIcon.Bridge]: bridgeIcon,
[LuciferIcon.Check]: checkIcon,
[LuciferIcon.Cog]: cogIcon,
[LuciferIcon.Cross]: crossIcon,
[LuciferIcon.Bulb]: lightBulbIcon,
[LuciferIcon.BulbGroup]: lightBulbGroupIcon,
[LuciferIcon.Hexagon]: hexagonIcon,
[LuciferIcon.HexagonGroup]: hexagonGroupIcon,
[LuciferIcon.Router]: routerIcon,
[LuciferIcon.Square]: squareIcon,
[LuciferIcon.Switch]: lightSwitchIcon,
[LuciferIcon.Triangle]: triangleIcon,
}
export function toIconify(icon: LuciferIcon): object {

3
webui/src/pages/SettingsPage.tsx

@ -2,10 +2,9 @@ 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);
const {colorPresets} = useContext(DataContext);
useEffect(() => {
}, []);

20
webui/src/primitives/Elements.tsx

@ -12,13 +12,12 @@ function Element({children}: React.PropsWithChildren<{}>) {
)
}
interface ColorElementProps {
caption: string
interface IconProps {
icon?: LuciferIcon
color?: ColorValue | "hs-gradient" | "k-gradient"
}
export function IconElement({caption, color, icon}: ColorElementProps) {
export function IconBlock({icon, color}: IconProps) {
const style: CSSProperties = useMemo(() => {
const s: CSSProperties = {
backgroundClip: "text",
@ -44,9 +43,22 @@ export function IconElement({caption, color, icon}: ColorElementProps) {
return s;
}, [color]);
return (
<Icon icon={toIconify(icon || LuciferIcon.Square)}
className="ColorElement-block"
style={style}
/>
);
}
interface IconElementProps extends IconProps {
caption: string
}
export function IconElement({caption, color, icon}: IconElementProps) {
return (
<Element>
<Icon icon={toIconify(icon || LuciferIcon.Square)} className="ColorElement-block" style={style}/>
<IconBlock icon={icon} color={color}/>
{caption}
</Element>
);

69
webui/src/primitives/Layout.sass

@ -1,7 +1,7 @@
@import "../res/colors"
.Tabs-container
background-color: $color-foreground-dark
background-color: $color-background-elevated
&.Tabs-large
text-align: center
@ -22,4 +22,69 @@
.Page
max-width: 800px
margin: 0.25em auto
margin: 0.25em auto
.Dialog-overlay
background-color: rgba(0, 0, 0, 0.5)
position: fixed
left: 0
top: 0
right: 0
bottom: 0
overflow-y: scroll
.Dialog-main
max-width: 640px
margin: 4em auto
// background-color: #202020
color: #ddd
outline: none
box-shadow: 5px 10px 20px rgba(0, 0, 0, 0.5)
.Card
margin: 0
.Dialog-title
font-weight: 800
font-size: 200%
text-align: center
padding: 0.25em 0.5ch
.Dialog-body
margin-bottom: 0
.Dialog-footer
border-top: solid 1px #000
.Dialog-container
background-color: $color-background
.Dialog-header
font-size: 140%
font-weight: bold
text-align: center
padding: 0.1em 0
background-color: $color-background-elevated
.Dialog-footer
display: flex
flex-direction: row
button
flex-grow: 4
text-align: center
font: inherit
padding: 0.5em 1.5ch
background-color: $color-background-elevated
color: $color-foreground
border: none
cursor: pointer
&:hover, &:active
background-color: $color-background-elevated2
color: $color-foreground-elevated
&:not(:last-of-type)
border-right: solid 1px #000

41
webui/src/primitives/Layout.tsx

@ -1,5 +1,8 @@
import React, {PropsWithChildren, useCallback, useEffect} from "react";
import ReactModal from "react-modal";
import "./Layout.sass";
import {LuciferIcon} from "../models/Icons";
import {IconBlock} from "./Elements";
interface TabsProps {
tabNames: string[]
@ -36,11 +39,11 @@ export function Tabs({tabNames, index, onChange, large, boldIndex}: TabsProps) {
<div className={`Tabs-container ${large ? "Tabs-large" : ""}`}>
<div className="Tabs-inner">
{tabNames.map((name, i) => (
<div className={tabClass(i)}
onClick={() => onChange(i)}
>
{name}
</div>
<div className={tabClass(i)}
onClick={() => onChange(i)}
>
{name}
</div>
))}
</div>
</div>
@ -54,7 +57,7 @@ interface PageProps {
export function Page({title, children}: PropsWithChildren<PageProps>) {
useEffect(() => {
window.document.title = title ? `${title} - Lucifer` : "Lucifer";
}, []);
}, [title]);
return (
<div className="Page">
@ -67,13 +70,35 @@ interface DialogProps {
title: string
buttons?: {
text: string
icon: LuciferIcon
onClick?: (() => void)
}[]
loading?: boolean
}
export function Dialog({}: PropsWithChildren<DialogProps>) {
export function Dialog({title, buttons, children}: PropsWithChildren<DialogProps>) {
return undefined;
return (
<ReactModal isOpen={true}
className="Dialog-main"
overlayClassName="Dialog-overlay"
>
<div className="Dialog-container">
<div className="Dialog-header">
{title}
</div>
{children}
<div className="Dialog-footer">
{buttons?.map(b => (
<button key={b.text}>
<IconBlock icon={b.icon}/>
{" "}
{b.text}
</button>
))}
</div>
</div>
</ReactModal>
);
}

3
webui/src/res/colors.sass

@ -1,8 +1,11 @@
$color-background: #111
$color-background-elevated: #222
$color-background-elevated2: #333
$color-foreground: rgb(204, 204, 204)
$color-foreground-elevated: #FFF
$color-foreground-dark: rgba(238, 238, 238, 0.05)
body, html
background-color: $color-background
color: $color-foreground

19
webui/yarn.lock

@ -1829,6 +1829,13 @@
dependencies:
"@types/react" "*"
"@types/react-modal@^3.12.0":
version "3.12.0"
resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-3.12.0.tgz#91fa86a76fd7fc57e36d2cf9b76efe5735a855a1"
integrity sha512-UnHu/YO73NZLwqFpju/c0tqiswR0UHIfeO16rkfLVJUIMfQsl7X0CBm99H5XXgBMe/PgtQ/Rkud72iuRBr1TeA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^17.0.0":
version "17.0.6"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.6.tgz#0ec564566302c562bf497d73219797a5e0297013"
@ -9193,7 +9200,7 @@ prompts@2.4.0, prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.5.10, prop-types@^15.7.2:
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -9433,13 +9440,13 @@ react-lifecycles-compat@^3.0.0:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-modal@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.13.1.tgz#a02dce63bbfee7582936f1e9835d518ef8f56453"
integrity sha512-m6yXK7I4YKssQnsjHK7xITSXy2O81BSOHOsg0/uWAsdKtuT9HF2tdoYhRuxNNQg2V+LgepsoHUPJKS8m6no+eg==
react-modal@^3.14.2:
version "3.14.2"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.14.2.tgz#e04e1119d64c9fbb3db80736eb80ea9372f28778"
integrity sha512-CYasEJanwneDsmvtx/fisXhgDxtt3I8jWTVX/tP9dM/J1NgDKU9lgjR9zuCCl33ub2jrTWhXyijCxCzYGN8sJg==
dependencies:
exenv "^1.2.0"
prop-types "^15.5.10"
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.0"
warning "^4.0.3"

Loading…
Cancel
Save