Browse Source

dialogs sorta work now

pull/1/head
Stian Fredrik Aune 4 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. 67
      webui/src/primitives/Layout.sass
  9. 31
      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", "node-sass": "4.14.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-modal": "^3.13.1",
"react-modal": "^3.14.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-semantic-ui-range": "^0.7.1", "react-semantic-ui-range": "^0.7.1",
"typescript": "^4.1.2", "typescript": "^4.1.2",
@ -50,6 +50,7 @@
] ]
}, },
"devDependencies": { "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 {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 {IconElement} from "./primitives/Elements";
import SettingsPage from "./pages/SettingsPage"; import SettingsPage from "./pages/SettingsPage";
import {LuciferIcon} from "./models/Icons"; import {LuciferIcon} from "./models/Icons";
import ReactModal from "react-modal";
import DialogContext, {DialogContextProvider, makeDialog} from "./contexts/DialogContext";
const routeObj: HookRouter.RouteObject = { 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": () => ( "/devices": () => (
<div> <div>
@ -19,12 +27,13 @@ const routeObj: HookRouter.RouteObject = {
<IconElement icon={LuciferIcon.Bulb} caption="Kelvin 2k" color={{kelvin: 2000}}/> <IconElement icon={LuciferIcon.Bulb} caption="Kelvin 2k" color={{kelvin: 2000}}/>
<IconElement icon={LuciferIcon.BulbGroup} caption="Kelvin 3k" color={{kelvin: 3000}}/> <IconElement icon={LuciferIcon.BulbGroup} caption="Kelvin 3k" color={{kelvin: 3000}}/>
<IconElement icon={LuciferIcon.Bridge} caption="Kelvin 4k" color={{kelvin: 4000}}/> <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="hs-gradient"/>
<IconElement caption="Mer" color="k-gradient"/> <IconElement caption="Mer" color="k-gradient"/>
</div> </div>
@ -37,8 +46,21 @@ const routeList = ["/", "/devices", "/settings"];
const tabNames = ["Lucifer", "Enheter", "Oppsett"]; const tabNames = ["Lucifer", "Enheter", "Oppsett"];
function App() { function App() {
return (
<DialogContextProvider>
<AppInContext/>
</DialogContextProvider>
);
}
function AppInContext() {
const route = useRoutes(routeObj); const route = useRoutes(routeObj);
const path = usePath(); const path = usePath();
const {dialog} = useContext(DialogContext);
useLayoutEffect(() => {
ReactModal.setAppElement("#root");
}, []);
return ( return (
<div className="App"> <div className="App">
@ -47,7 +69,8 @@ function App() {
onChange={i => navigate(routeList[i])} onChange={i => navigate(routeList[i])}
large boldIndex={0} large boldIndex={0}
/> />
{route || <div>B</div>}
{route || <Page>404</Page>}
{makeDialog(dialog)}
</div> </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 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 hexagonIcon from "@iconify-icons/mdi/hexagon";
import hexagonGroupIcon from "@iconify-icons/mdi/hexagon-multiple";
import lightBulbIcon from "@iconify-icons/mdi/lightbulb"; import lightBulbIcon from "@iconify-icons/mdi/lightbulb";
import lightBulbGroupIcon from "@iconify-icons/mdi/lightbulb-group"; 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 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: // To add a new icon, follow the instructions on this page:
// https://iconify.design/icon-sets/mdi/ // https://iconify.design/icon-sets/mdi/
export enum LuciferIcon { export enum LuciferIcon {
Bridge = "Bridge", Bridge = "Bridge",
Check = "Check",
Cog = "Cog",
Cross = "Cross",
Bulb = "Bulb", Bulb = "Bulb",
BulbGroup = "BulbGroup", BulbGroup = "BulbGroup",
Hexagon = "Hexagon", Hexagon = "Hexagon",
HexagonGroup = "HexagonGroup",
Router = "Router",
Square = "Square", Square = "Square",
Switch = "Switch",
Triangle = "Triangle",
} }
const iconMap = { const iconMap = {
[LuciferIcon.Bridge]: bridgeIcon, [LuciferIcon.Bridge]: bridgeIcon,
[LuciferIcon.Check]: checkIcon,
[LuciferIcon.Cog]: cogIcon,
[LuciferIcon.Cross]: crossIcon,
[LuciferIcon.Bulb]: lightBulbIcon, [LuciferIcon.Bulb]: lightBulbIcon,
[LuciferIcon.BulbGroup]: lightBulbGroupIcon, [LuciferIcon.BulbGroup]: lightBulbGroupIcon,
[LuciferIcon.Hexagon]: hexagonIcon, [LuciferIcon.Hexagon]: hexagonIcon,
[LuciferIcon.HexagonGroup]: hexagonGroupIcon,
[LuciferIcon.Router]: routerIcon,
[LuciferIcon.Square]: squareIcon, [LuciferIcon.Square]: squareIcon,
[LuciferIcon.Switch]: lightSwitchIcon,
[LuciferIcon.Triangle]: triangleIcon,
} }
export function toIconify(icon: LuciferIcon): object { 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 {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 {addColorPreset} from "../actions/ColorPresets";
const SettingsPage: React.FC = () => { const SettingsPage: React.FC = () => {
const {colorPresets, refresh} = useContext(DataContext);
const {colorPresets} = useContext(DataContext);
useEffect(() => { useEffect(() => {
}, []); }, []);

20
webui/src/primitives/Elements.tsx

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

67
webui/src/primitives/Layout.sass

@ -1,7 +1,7 @@
@import "../res/colors" @import "../res/colors"
.Tabs-container .Tabs-container
background-color: $color-foreground-dark
background-color: $color-background-elevated
&.Tabs-large &.Tabs-large
text-align: center text-align: center
@ -23,3 +23,68 @@
.Page .Page
max-width: 800px 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

31
webui/src/primitives/Layout.tsx

@ -1,5 +1,8 @@
import React, {PropsWithChildren, useCallback, useEffect} from "react"; import React, {PropsWithChildren, useCallback, useEffect} from "react";
import ReactModal from "react-modal";
import "./Layout.sass"; import "./Layout.sass";
import {LuciferIcon} from "../models/Icons";
import {IconBlock} from "./Elements";
interface TabsProps { interface TabsProps {
tabNames: string[] tabNames: string[]
@ -54,7 +57,7 @@ interface PageProps {
export function Page({title, children}: PropsWithChildren<PageProps>) { export function Page({title, children}: PropsWithChildren<PageProps>) {
useEffect(() => { useEffect(() => {
window.document.title = title ? `${title} - Lucifer` : "Lucifer"; window.document.title = title ? `${title} - Lucifer` : "Lucifer";
}, []);
}, [title]);
return ( return (
<div className="Page"> <div className="Page">
@ -67,13 +70,35 @@ interface DialogProps {
title: string title: string
buttons?: { buttons?: {
text: string text: string
icon: LuciferIcon
onClick?: (() => void) onClick?: (() => void)
}[] }[]
loading?: boolean 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: #111
$color-background-elevated: #222 $color-background-elevated: #222
$color-background-elevated2: #333
$color-foreground: rgb(204, 204, 204) $color-foreground: rgb(204, 204, 204)
$color-foreground-elevated: #FFF
$color-foreground-dark: rgba(238, 238, 238, 0.05) $color-foreground-dark: rgba(238, 238, 238, 0.05)
body, html body, html
background-color: $color-background background-color: $color-background
color: $color-foreground color: $color-foreground

19
webui/yarn.lock

@ -1829,6 +1829,13 @@
dependencies: dependencies:
"@types/react" "*" "@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": "@types/react@*", "@types/react@^17.0.0":
version "17.0.6" version "17.0.6"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.6.tgz#0ec564566302c562bf497d73219797a5e0297013" 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" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.5.10, prop-types@^15.7.2:
prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== 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" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== 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: dependencies:
exenv "^1.2.0" exenv "^1.2.0"
prop-types "^15.5.10"
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.0" react-lifecycles-compat "^3.0.0"
warning "^4.0.3" warning "^4.0.3"

Loading…
Cancel
Save