Gisle Aune
2 years ago
14 changed files with 4049 additions and 107 deletions
-
3561frontend/package-lock.json
-
2frontend/package.json
-
23frontend/src/lib/client/lucifer.ts
-
23frontend/src/lib/components/ColorPalette.svelte
-
194frontend/src/lib/components/Lamp.svelte
-
75frontend/src/lib/components/Toolbar.svelte
-
54frontend/src/lib/contexts/SelectContext.svelte
-
57frontend/src/lib/contexts/StateContext.svelte
-
12frontend/src/lib/models/color.ts
-
59frontend/src/lib/models/device.ts
-
60frontend/src/lib/models/palette.ts
-
5frontend/src/lib/models/uistate.ts
-
10frontend/src/routes/+layout.svelte
-
19frontend/src/routes/+page.svelte
3561
frontend/package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,23 @@ |
|||||
|
import type UIState from "$lib/models/uistate"; |
||||
|
|
||||
|
export default async function fetchLucifer<T>(path: string, init?: RequestInit): Promise<T> { |
||||
|
const url = import.meta.env.VITE_LUCIFER4_BACKEND_URL + "/" + path; |
||||
|
console.log(url); |
||||
|
|
||||
|
const res = await fetch(url, init); |
||||
|
if (res.status !== 200) { |
||||
|
if (res.headers.get("Content-Type")?.includes("application/json")) { |
||||
|
throw await res.json(); |
||||
|
} else { |
||||
|
throw await res.text(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const json = await res.json(); |
||||
|
|
||||
|
return json as T; |
||||
|
} |
||||
|
|
||||
|
export async function fetchUIState(): Promise<UIState> { |
||||
|
return fetchLucifer("state"); |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
<script lang="ts"> |
||||
|
import { rgbString } from "$lib/models/color"; |
||||
|
import type { Palette } from "$lib/models/palette"; |
||||
|
|
||||
|
export let palette: Palette |
||||
|
</script> |
||||
|
|
||||
|
<div class="palette"> |
||||
|
{#each palette as p (p.value)} |
||||
|
<div title={p.label} class="color" style="background-color: {rgbString(p.rgb)}"></div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
<style lang="sass"> |
||||
|
div.palette |
||||
|
display: flex |
||||
|
flex-direction: row |
||||
|
|
||||
|
div.color |
||||
|
margin: 0.05em |
||||
|
width: 1ch |
||||
|
height: 0.5em |
||||
|
</style> |
@ -1,76 +1,162 @@ |
|||||
<script lang="ts"> |
<script lang="ts"> |
||||
import type { ColorRGB } from "../models/color"; |
|
||||
|
import { getSelectedContext } from "$lib/contexts/SelectContext.svelte"; |
||||
|
import type Device from "$lib/models/device"; |
||||
|
import { SupportFlags } from "$lib/models/device"; |
||||
|
import { rgb, type ColorRGB } from "../models/color"; |
||||
|
|
||||
export let selected: boolean = false; |
|
||||
export let title: string; |
|
||||
export let color: ColorRGB; |
|
||||
export let intensity: number; |
|
||||
|
export let device: Device; |
||||
|
|
||||
|
const {selectedMap, toggleSelection} = getSelectedContext(); |
||||
|
|
||||
|
function onSelect() { |
||||
|
toggleSelection(device.id); |
||||
|
} |
||||
|
|
||||
|
// Process device |
||||
|
let deviceTitle: string; |
||||
|
let iconColor: ColorRGB | null; |
||||
|
let barColor: ColorRGB | null; |
||||
|
let barPercentage: number | null; |
||||
|
let roundboiText: string | null; |
||||
|
$: { |
||||
|
// TODO: Fix device.name on the backend |
||||
|
const nameAlias = device.aliases.find(a => a.startsWith("lucifer:name:")); |
||||
|
if (nameAlias != null) { |
||||
|
deviceTitle = nameAlias.slice("lucifer:name:".length); |
||||
|
} else { |
||||
|
deviceTitle = ""; |
||||
|
} |
||||
|
|
||||
|
barPercentage = null; |
||||
|
barColor = null; |
||||
|
iconColor = null; |
||||
|
|
||||
|
if (device.hwState) { |
||||
|
const hws = device.hwState; |
||||
|
const sflags = hws.supportFlags; |
||||
|
|
||||
|
if (deviceTitle == "") { |
||||
|
deviceTitle = device.hwState.internalName; |
||||
|
} |
||||
|
|
||||
|
if (sflags & SupportFlags.Color) { |
||||
|
iconColor = device.activeColorRgb; |
||||
|
} else { |
||||
|
iconColor = null; |
||||
|
} |
||||
|
|
||||
|
if (sflags & SupportFlags.SensorPresence) { |
||||
|
barColor = rgb(0.5,0.5,0.5); |
||||
|
barPercentage = Math.max(0, (300 - (device.sensors.lastMotion||300))/300) * 100; |
||||
|
} |
||||
|
|
||||
|
if (sflags & SupportFlags.Temperature) { |
||||
|
barColor = rgb(1.000,0.2,0.2); |
||||
|
barPercentage = Math.min(1, Math.max(0, (device.desiredState.temperature||0) - 5) / 35) * 100; |
||||
|
} |
||||
|
|
||||
|
if (sflags & SupportFlags.Intensity) { |
||||
|
if (sflags & SupportFlags.Color) { |
||||
|
barColor = device.activeColorRgb; |
||||
|
} else { |
||||
|
barColor = rgb(1.000,0.671,0.355); |
||||
|
} |
||||
|
|
||||
|
barPercentage = device.desiredState.intensity; |
||||
|
} |
||||
|
|
||||
|
if (sflags & SupportFlags.SensorTemperature && !!device.sensors.temperature) { |
||||
|
roundboiText = `${device.sensors.temperature.toFixed(0)}°` |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Make title visible |
||||
let displayTitle: string; |
let displayTitle: string; |
||||
$: { |
$: { |
||||
if (title.length > 12) { |
|
||||
let last = title.split(" ").pop() || title; |
|
||||
|
if (deviceTitle.length > 12) { |
||||
|
let last = deviceTitle.split(" ").pop() || deviceTitle; |
||||
if (last.length > 4) { |
if (last.length > 4) { |
||||
last = last.slice(-3); |
last = last.slice(-3); |
||||
} |
} |
||||
|
|
||||
displayTitle = `${(title.split(" ").shift()||"").slice(0, 10 - last.length)}... ${last}` |
|
||||
|
displayTitle = `${(deviceTitle.split(" ").shift()||"").slice(0, 10 - last.length)}... ${last}` |
||||
} else { |
} else { |
||||
displayTitle = title; |
|
||||
|
displayTitle = deviceTitle; |
||||
} |
} |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<div class="lamp" class:selected> |
|
||||
|
<!-- svelte-ignore a11y-click-events-have-key-events --> |
||||
|
<div class="lamp" class:selected={$selectedMap[device.id]} on:click={onSelect}> |
||||
<div class="row"> |
<div class="row"> |
||||
<div style="background-color: rgb({color.red*255},{color.green*255},{color.blue*255})" class="roundboi"></div> |
|
||||
|
{#if iconColor != null} |
||||
|
<div style="background-color: rgb({iconColor.red*255},{iconColor.green*255},{iconColor.blue*255})" class="roundboi"></div> |
||||
|
{:else} |
||||
|
<div style="background-color: #223)" class="roundboi"> |
||||
|
{#if !!roundboiText} |
||||
|
<div class="roundboi-text">{roundboiText}</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
<div class="title">{displayTitle}</div> |
<div class="title">{displayTitle}</div> |
||||
</div> |
</div> |
||||
<div class="flatboi"> |
<div class="flatboi"> |
||||
<div style="width: {intensity*100}%; background-color: rgb({color.red*255},{color.green*255},{color.blue*255})" class="flatboi2"></div> |
|
||||
|
{#if barColor != null && barPercentage != null} |
||||
|
<div style="width: {barPercentage*100}%; background-color: rgb({barColor.red*255},{barColor.green*255},{barColor.blue*255})" class="flatboi2"></div> |
||||
|
{/if} |
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
|
|
||||
<style> |
|
||||
div.lamp { |
|
||||
width: 15ch; |
|
||||
height: 2em; |
|
||||
margin: 0.25em; |
|
||||
background: #18181c; |
|
||||
color: #84888f; |
|
||||
border-radius: 0.25em; |
|
||||
border-top-right-radius: 1em; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
div.lamp.selected { |
|
||||
background: #282833; |
|
||||
color: #cde; |
|
||||
} |
|
||||
div.lamp > div.row { |
|
||||
display: flex; |
|
||||
} |
|
||||
div.lamp > div.row > div.roundboi { |
|
||||
width: 1.25em; |
|
||||
height: 1.25em; |
|
||||
margin-left: 0.25em; |
|
||||
margin-top: 0.30em; |
|
||||
border-radius: 2em; |
|
||||
box-sizing: border-box; |
|
||||
border: 0.5px solid #000; |
|
||||
} |
|
||||
div.lamp > div.row > div.title { |
|
||||
font-size: 0.9em; |
|
||||
text-align: left; |
|
||||
margin-top: 0.4em; |
|
||||
margin-left: 0.75ch; |
|
||||
margin-right: 1ch; |
|
||||
height: 1.6em; |
|
||||
} |
|
||||
div.lamp > div.flatboi { |
|
||||
height: 0.2em; |
|
||||
background-color: #040408; |
|
||||
} |
|
||||
div.lamp > div.flatboi > div.flatboi2 { |
|
||||
height: 0.2em; |
|
||||
} |
|
||||
|
<style lang="sass"> |
||||
|
div.lamp |
||||
|
cursor: pointer |
||||
|
width: 19ch |
||||
|
height: 2em |
||||
|
margin: 0.25em |
||||
|
background: #18181c |
||||
|
color: #84888f |
||||
|
border-radius: 0.25em |
||||
|
border-top-right-radius: 1em |
||||
|
overflow: hidden |
||||
|
box-shadow: 1px 1px 1px #000 |
||||
|
|
||||
|
@media screen and (max-width: 700px) |
||||
|
width: 100% |
||||
|
|
||||
|
&.selected |
||||
|
background: #282833 |
||||
|
color: #cde |
||||
|
|
||||
|
> div.row |
||||
|
display: flex |
||||
|
|
||||
|
> div.roundboi |
||||
|
width: 1.25em |
||||
|
height: 1.25em |
||||
|
margin-left: 0.25em |
||||
|
margin-top: 0.30em |
||||
|
border-radius: 2em |
||||
|
box-sizing: border-box |
||||
|
border: 0.5px solid #000 |
||||
|
|
||||
|
> div.roundboi-text |
||||
|
color: #abc |
||||
|
margin-top: 0.2em |
||||
|
font-size: 0.8em |
||||
|
|
||||
|
> div.title |
||||
|
font-size: 0.9em |
||||
|
text-align: left |
||||
|
margin-top: 0.4em |
||||
|
margin-left: 0.75ch |
||||
|
margin-right: 1ch |
||||
|
height: 1.6em |
||||
|
|
||||
|
> div.flatboi |
||||
|
height: 0.2em |
||||
|
|
||||
|
> div.flatboi2 |
||||
|
height: 0.2em |
||||
</style> |
</style> |
@ -0,0 +1,75 @@ |
|||||
|
<script lang="ts"> |
||||
|
import { getSelectedContext } from "$lib/contexts/SelectContext.svelte"; |
||||
|
import { getStateContext } from "$lib/contexts/StateContext.svelte"; |
||||
|
import type Device from "$lib/models/device"; |
||||
|
import { KELVIN_PALETTE, RGB_PALETTE } from "$lib/models/palette"; |
||||
|
import ColorPalette from "./ColorPalette.svelte"; |
||||
|
|
||||
|
const {deviceList} = getStateContext(); |
||||
|
const {selectedMap} = getSelectedContext(); |
||||
|
|
||||
|
let selected: Device[]; |
||||
|
let unselected: Device[]; |
||||
|
$: selected = $deviceList.filter(d => !!$selectedMap[d.id]); |
||||
|
$: unselected = $deviceList.filter(d => !$selectedMap[d.id]); |
||||
|
|
||||
|
let assignment: string; |
||||
|
$: { |
||||
|
assignment = ""; |
||||
|
if (selected.length > 0) { |
||||
|
for (const alias of selected[0].aliases) { |
||||
|
if (!selected.find(d => !d.aliases.includes(alias)) && !unselected.find(d => d.aliases.includes(alias))) { |
||||
|
assignment = alias; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (assignment === "") { |
||||
|
if (selected.length == 1) { |
||||
|
assignment = selected[0].id; |
||||
|
} else { |
||||
|
let prefix = selected[0].id; |
||||
|
for (const device of selected) { |
||||
|
for (let i = 0; i < prefix.length; i++) { |
||||
|
if (device.id.charAt(i) !== prefix.charAt(i)) { |
||||
|
prefix = prefix.slice(0, i); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (prefix.length > 0) { |
||||
|
assignment = `${prefix}{${selected.map(d => d.id.slice(prefix.length)).join("|")}}` |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (assignment === "") { |
||||
|
assignment = `{${selected.map(d => d.id).join("|")}}` |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="toolbar"> |
||||
|
<ColorPalette palette={RGB_PALETTE} /> |
||||
|
</div> |
||||
|
|
||||
|
<style lang="sass"> |
||||
|
div.toolbar |
||||
|
position: fixed |
||||
|
bottom: 0 |
||||
|
|
||||
|
width: 100% |
||||
|
height: 2em |
||||
|
padding: 0.35em 1ch |
||||
|
box-sizing: border-box |
||||
|
font-size: 1.5em |
||||
|
background: #18181c |
||||
|
color: #abc |
||||
|
box-shadow: 1px 1px 1px #000 |
||||
|
|
||||
|
display: flex |
||||
|
flex-direction: row |
||||
|
flex-wrap: wrap |
||||
|
</style> |
@ -0,0 +1,54 @@ |
|||||
|
<script lang="ts" context="module"> |
||||
|
import { getContext, setContext } from "svelte"; |
||||
|
import { writable, type Readable } from "svelte/store"; |
||||
|
import { getStateContext } from "./StateContext.svelte"; |
||||
|
|
||||
|
const ctxKey = {ctx: "SelectContext"}; |
||||
|
|
||||
|
export interface SelectContextData { |
||||
|
toggleSelection(id: string): void |
||||
|
selectedList: Readable<string[]> |
||||
|
selectedMap: Readable<{[id:string]: boolean}> |
||||
|
} |
||||
|
|
||||
|
export function getSelectedContext(): SelectContextData { |
||||
|
return getContext(ctxKey) as SelectContextData; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
const selectedList = writable<string[]>([]); |
||||
|
const selectedMap = writable<{[id:string]: boolean}>({}); |
||||
|
|
||||
|
const {state} = getStateContext(); |
||||
|
|
||||
|
function toggleSelection(id: string) { |
||||
|
selectedMap.update(m => ({...m, [id]: !m[id]})); |
||||
|
} |
||||
|
|
||||
|
// Effect: remove non-existent devices |
||||
|
$: { |
||||
|
const newMap = {...$selectedMap}; |
||||
|
for (const id in newMap) { |
||||
|
if (!Object.hasOwn(newMap, id)) { |
||||
|
continue; |
||||
|
} |
||||
|
if (!$state?.devices[id]) { |
||||
|
delete newMap[id]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$selectedMap = newMap; |
||||
|
} |
||||
|
|
||||
|
// Effect: sync list |
||||
|
$: $selectedList = Object.keys($selectedMap).filter(id => !!$selectedMap[id]); |
||||
|
|
||||
|
setContext(ctxKey, { |
||||
|
selectedList: {subscribe: selectedList.subscribe}, |
||||
|
selectedMap: {subscribe: selectedMap.subscribe}, |
||||
|
toggleSelection, |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<slot></slot> |
@ -0,0 +1,57 @@ |
|||||
|
<script lang="ts" context="module"> |
||||
|
import { fetchUIState } from "$lib/client/lucifer"; |
||||
|
import type Device from "$lib/models/device"; |
||||
|
import type UIState from "$lib/models/uistate"; |
||||
|
import { getContext, onMount, setContext } from "svelte"; |
||||
|
import { derived, writable, type Readable } from "svelte/store"; |
||||
|
|
||||
|
const ctxKey = {ctx: "StateContext"}; |
||||
|
|
||||
|
export interface StateContextData { |
||||
|
reload(): Promise<void> |
||||
|
state: Readable<UIState | null> |
||||
|
error: Readable<string | null> |
||||
|
deviceList: Readable<Device[]> |
||||
|
} |
||||
|
|
||||
|
export function getStateContext(): StateContextData { |
||||
|
return getContext(ctxKey) as StateContextData; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
const state = writable<UIState | null>(null); |
||||
|
const error = writable<string | null>(null); |
||||
|
|
||||
|
const deviceList = derived(state, state => { |
||||
|
if (state == null) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
return Object.keys(state.devices) |
||||
|
.map(k => state.devices[k]) |
||||
|
.sort((a,b) => a.id.localeCompare(b.id)); |
||||
|
}) |
||||
|
|
||||
|
async function reload() { |
||||
|
error.set(null); |
||||
|
|
||||
|
try { |
||||
|
const newState = await fetchUIState(); |
||||
|
state.set(newState); |
||||
|
} catch(err) { |
||||
|
error.set(err?.toString ? err.toString() : String(err)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMount(() => { reload(); }); |
||||
|
|
||||
|
setContext<StateContextData>(ctxKey, { |
||||
|
reload, |
||||
|
error: { subscribe: error.subscribe }, |
||||
|
state: { subscribe: state.subscribe }, |
||||
|
deviceList, |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<slot></slot> |
@ -0,0 +1,59 @@ |
|||||
|
import type { ColorFlags, ColorRGB } from "./color" |
||||
|
|
||||
|
export default interface Device { |
||||
|
id: string |
||||
|
name: string |
||||
|
hwMetadata: HardwareMetadata | null |
||||
|
hwState: HardwareState | null |
||||
|
activeColorRgb: ColorRGB | null |
||||
|
desiredState: State, |
||||
|
aliases: string[], |
||||
|
assignment: string, |
||||
|
sensors: Sensors |
||||
|
} |
||||
|
|
||||
|
export interface Sensors { |
||||
|
lastMotion?: number |
||||
|
temperature?: number |
||||
|
} |
||||
|
|
||||
|
export interface HardwareState { |
||||
|
id: string |
||||
|
internalName: string |
||||
|
supportFlags: SupportFlags |
||||
|
colorFlags: ColorFlags |
||||
|
buttons: string[] | null |
||||
|
state: State |
||||
|
batteryPercentage: number |
||||
|
unreachable: boolean |
||||
|
} |
||||
|
|
||||
|
export interface HardwareMetadata { |
||||
|
firmwareVersion?: string |
||||
|
} |
||||
|
|
||||
|
export interface State { |
||||
|
power: boolean | null |
||||
|
temperature: number | null |
||||
|
intensity: number | null |
||||
|
color: string | null |
||||
|
} |
||||
|
|
||||
|
export enum SupportFlags { |
||||
|
Power = 1 << 0, |
||||
|
Intensity = 1 << 1, |
||||
|
Color = 1 << 2, |
||||
|
Temperature = 1 << 3, |
||||
|
SensorButtons = 1 << 4, |
||||
|
SensorTemperature = 1 << 5, |
||||
|
SensorLightLevel = 1 << 6, |
||||
|
SensorPresence = 1 << 7, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export const BLANK_STATE: State = (Object.seal||(v=>v))({ |
||||
|
power: null, |
||||
|
temperature: null, |
||||
|
intensity: null, |
||||
|
color: null, |
||||
|
}); |
@ -0,0 +1,60 @@ |
|||||
|
import { rgb, type ColorRGB } from "./color" |
||||
|
|
||||
|
export type Palette = { value: string, label: string, rgb: ColorRGB }[]; |
||||
|
|
||||
|
export const RGB_PALETTE: Palette = [ |
||||
|
{value: "hs:235.00,0.550", label: "Sky Blue", rgb: rgb(0.450,0.496,1.000) }, |
||||
|
{value: "hs:235.00,0.330", label: "Sky Blue 2", rgb: rgb(0.670,0.698,1.000) }, |
||||
|
{value: "xy:0.2721,0.1908", label: "Lilac", rgb: rgb(0.792,0.500,1.000) }, |
||||
|
{value: "hs:220,0.1", label: "Bluish White", rgb: rgb(0.900,0.933,1.000)} |
||||
|
] |
||||
|
|
||||
|
export const KELVIN_PALETTE: Palette = [ |
||||
|
{value: "k:1500", label: "1500 K", rgb: rgb(1.000,0.427,0.000)}, |
||||
|
{value: "k:1625", label: "1625 K", rgb: rgb(1.000,0.469,0.000)}, |
||||
|
{value: "k:1750", label: "1750 K", rgb: rgb(1.000,0.484,0.000)}, |
||||
|
{value: "k:1875", label: "1875 K", rgb: rgb(1.000,0.499,0.000)}, |
||||
|
{value: "k:2000", label: "2000 K", rgb: rgb(1.000,0.541,0.071)}, |
||||
|
{value: "k:2125", label: "2125 K", rgb: rgb(1.000,0.571,0.162)}, |
||||
|
{value: "k:2250", label: "2250 K", rgb: rgb(1.000,0.586,0.193)}, |
||||
|
{value: "k:2375", label: "2375 K", rgb: rgb(1.000,0.601,0.221)}, |
||||
|
{value: "k:2500", label: "2500 K", rgb: rgb(1.000,0.631,0.282)}, |
||||
|
{value: "k:2625", label: "2625 K", rgb: rgb(1.000,0.659,0.333)}, |
||||
|
{value: "k:2750", label: "2750 K", rgb: rgb(1.000,0.671,0.355)}, |
||||
|
{value: "k:2875", label: "2875 K", rgb: rgb(1.000,0.682,0.376)}, |
||||
|
{value: "k:3000", label: "3000 K", rgb: rgb(1.000,0.706,0.420)}, |
||||
|
{value: "k:3125", label: "3125 K", rgb: rgb(1.000,0.730,0.465)}, |
||||
|
{value: "k:3250", label: "3250 K", rgb: rgb(1.000,0.739,0.482)}, |
||||
|
{value: "k:3375", label: "3375 K", rgb: rgb(1.000,0.748,0.500)}, |
||||
|
{value: "k:3500", label: "3500 K", rgb: rgb(1.000,0.769,0.537)}, |
||||
|
{value: "k:3625", label: "3625 K", rgb: rgb(1.000,0.786,0.575)}, |
||||
|
{value: "k:3750", label: "3750 K", rgb: rgb(1.000,0.794,0.590)}, |
||||
|
{value: "k:3875", label: "3875 K", rgb: rgb(1.000,0.802,0.606)}, |
||||
|
{value: "k:4000", label: "4000 K", rgb: rgb(1.000,0.820,0.639)}, |
||||
|
{value: "k:4125", label: "4125 K", rgb: rgb(1.000,0.833,0.673)}, |
||||
|
{value: "k:4250", label: "4250 K", rgb: rgb(1.000,0.839,0.686)}, |
||||
|
{value: "k:4375", label: "4375 K", rgb: rgb(1.000,0.845,0.699)}, |
||||
|
{value: "k:4500", label: "4500 K", rgb: rgb(1.000,0.859,0.729)}, |
||||
|
{value: "k:4625", label: "4625 K", rgb: rgb(1.000,0.873,0.757)}, |
||||
|
{value: "k:4750", label: "4750 K", rgb: rgb(1.000,0.879,0.768)}, |
||||
|
{value: "k:4875", label: "4875 K", rgb: rgb(1.000,0.884,0.780)}, |
||||
|
{value: "k:5000", label: "5000 K", rgb: rgb(1.000,0.894,0.808)}, |
||||
|
{value: "k:5125", label: "5125 K", rgb: rgb(1.000,0.908,0.832)}, |
||||
|
{value: "k:5250", label: "5250 K", rgb: rgb(1.000,0.912,0.843)}, |
||||
|
{value: "k:5375", label: "5375 K", rgb: rgb(1.000,0.916,0.854)}, |
||||
|
{value: "k:5500", label: "5500 K", rgb: rgb(1.000,0.925,0.878)}, |
||||
|
{value: "k:5625", label: "5625 K", rgb: rgb(1.000,0.936,0.899)}, |
||||
|
{value: "k:5750", label: "5750 K", rgb: rgb(1.000,0.939,0.908)}, |
||||
|
{value: "k:5875", label: "5875 K", rgb: rgb(1.000,0.943,0.917)}, |
||||
|
{value: "k:6000", label: "6000 K", rgb: rgb(1.000,0.953,0.937)}, |
||||
|
{value: "k:6125", label: "6125 K", rgb: rgb(1.000,0.960,0.958)}, |
||||
|
{value: "k:6250", label: "6250 K", rgb: rgb(1.000,0.963,0.965)}, |
||||
|
{value: "k:6375", label: "6375 K", rgb: rgb(1.000,0.967,0.973)}, |
||||
|
{value: "k:6500", label: "6500 K", rgb: rgb(1.000,0.976,0.992)}, |
||||
|
{value: "k:7000", label: "7000 K", rgb: rgb(0.961,0.953,1.000)}, |
||||
|
{value: "k:8000", label: "8000 K", rgb: rgb(0.890,0.914,1.000)}, |
||||
|
{value: "k:9000", label: "9000 K", rgb: rgb(0.839,0.882,1.000)}, |
||||
|
{value: "k:10000", label: "10000 K", rgb: rgb(0.812,0.855,1.000)}, |
||||
|
{value: "k:11000", label: "11000 K", rgb: rgb(0.784,0.835,1.000)}, |
||||
|
{value: "k:12000", label: "12000 K", rgb: rgb(0.765,0.820,1.000)}, |
||||
|
]; |
@ -0,0 +1,5 @@ |
|||||
|
import type Device from "./device"; |
||||
|
|
||||
|
export default interface UIState { |
||||
|
devices: {[id: string]: Device} |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
<script lang="ts"> |
||||
|
import SelectContext from "$lib/contexts/SelectContext.svelte"; |
||||
|
import StateContext from "$lib/contexts/StateContext.svelte"; |
||||
|
</script> |
||||
|
|
||||
|
<StateContext> |
||||
|
<SelectContext> |
||||
|
<slot></slot> |
||||
|
</SelectContext> |
||||
|
</StateContext> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue