You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
6.7 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. <script lang="ts" context="module">
  2. export const DARK_COLOR = rgb(0.5176470588235295/2, 0.5333333333333333/2, 0.5607843137254902/1.75);
  3. export const DARK_COLOR_SELECTED = rgb(0.5176470588235295/1.5, 0.5333333333333333/1.5, 0.5607843137254902/1.25);
  4. </script>
  5. <script lang="ts">
  6. import { getSelectedContext } from "$lib/contexts/SelectContext.svelte";
  7. import type Device from "$lib/models/device";
  8. import { SupportFlags } from "$lib/models/device";
  9. import { rgb, type ColorRGB } from "../models/color";
  10. import DeviceIcon from "./DeviceIcon.svelte";
  11. import Icon from "./Icon.svelte";
  12. export let device: Device;
  13. export let compact: boolean = false;
  14. const {selectedMap, toggleSelection} = getSelectedContext();
  15. function onSelect() {
  16. toggleSelection(device.id);
  17. }
  18. // Process device
  19. let deviceTitle: string;
  20. let iconColor: ColorRGB | null;
  21. let barColor: ColorRGB | null;
  22. let barFraction: number | null;
  23. let roundboiText: string | null;
  24. let roles: string[];
  25. let tags: string[];
  26. let hasRoleOrTag: boolean;
  27. $: {
  28. // TODO: Fix device.name on the backend
  29. const nameAlias = device.aliases?.find(a => a.startsWith("lucifer:name:"));
  30. if (nameAlias != null) {
  31. deviceTitle = nameAlias.slice("lucifer:name:".length);
  32. } else {
  33. deviceTitle = "";
  34. }
  35. roles = device.aliases?.filter(a => a.startsWith("lucifer:role:")).map(a => a.slice("lucifer:role:".length));
  36. tags = device.aliases?.filter(a => a.startsWith("lucifer:tag:")).map(a => a.slice("lucifer:tag:".length));
  37. hasRoleOrTag = !!(roles.length || tags.length)
  38. barFraction = null;
  39. barColor = null;
  40. iconColor = null;
  41. if (device.hwState) {
  42. const hws = device.hwState;
  43. const sflags = hws.supportFlags;
  44. const hasColor = !!(sflags & SupportFlags.Color);
  45. const hasPower = !!(sflags & SupportFlags.Power)
  46. if (deviceTitle == "") {
  47. deviceTitle = device.hwState.internalName;
  48. }
  49. if (hasColor && (!hasPower || device.desiredState?.power)) {
  50. iconColor = device.desiredColorRgb;
  51. } else {
  52. iconColor = null;
  53. }
  54. if (sflags & SupportFlags.SensorPresence) {
  55. barColor = rgb(0.5,0.5,0.5);
  56. barFraction = Math.max(0, (300 - (device.sensors.lastMotion!=null?device.sensors.lastMotion:300))/300);
  57. }
  58. if (sflags & SupportFlags.Temperature) {
  59. barColor = rgb(1.000,0.2,0.2);
  60. barFraction = Math.min(1, Math.max(0, (device.desiredState?.temperature||0) - 5) / 35) * 1;
  61. }
  62. if (sflags & SupportFlags.Intensity) {
  63. if (sflags & SupportFlags.Color) {
  64. barColor = device.desiredColorRgb;
  65. } else {
  66. barColor = rgb(1.000,0.671,0.355);
  67. }
  68. barFraction = device.desiredState?.intensity || 0;
  69. }
  70. if (sflags & SupportFlags.SensorTemperature && !!device.sensors.temperature) {
  71. roundboiText = `${device.sensors.temperature.toFixed(0)}°`
  72. }
  73. }
  74. }
  75. // Make title visible
  76. let displayTitle: string;
  77. $: {
  78. if (deviceTitle.length > 12) {
  79. let last = deviceTitle.split(" ").pop() || deviceTitle;
  80. if (last.length > 4) {
  81. last = last.slice(-3);
  82. }
  83. displayTitle = `${(deviceTitle.split(" ").shift()||"").slice(0, 10 - last.length)}... ${last}`
  84. } else {
  85. displayTitle = deviceTitle;
  86. }
  87. }
  88. let darkColor: ColorRGB;
  89. $: darkColor = $selectedMap[device.id] ? DARK_COLOR_SELECTED : DARK_COLOR
  90. </script>
  91. <!-- svelte-ignore a11y-click-events-have-key-events -->
  92. <div title={deviceTitle} class="lamp" class:compact class:selected={$selectedMap[device.id]} on:click={onSelect}>
  93. <div class="row">
  94. <div class="row-icon">
  95. {#if iconColor != null}
  96. <DeviceIcon name={device.icon||"generic_ball"} brightColor={iconColor} darkColor={darkColor} />
  97. {:else}
  98. <DeviceIcon name={device.icon||"generic_ball"} brightColor={darkColor} darkColor={darkColor}>
  99. {#if !!roundboiText}
  100. <div class="roundboi-text">{roundboiText}</div>
  101. {/if}
  102. </DeviceIcon>
  103. {/if}
  104. </div>
  105. <div class="title">
  106. <div class="name" class:hasRoleOrTag>{deviceTitle}</div>
  107. {#if hasRoleOrTag}
  108. <div class="tag-list">
  109. {#each roles as role}
  110. <div class="tag">
  111. <Icon block name="masks_theater" />
  112. <div class="tag-name">{role}</div>
  113. </div>
  114. {/each}
  115. {#each tags as tag}
  116. <div class="tag">
  117. <Icon block name="tag" />
  118. <div class="tag-name">{tag}</div>
  119. </div>
  120. {/each}
  121. </div>
  122. {/if}
  123. </div>
  124. </div>
  125. <div class="flatboi">
  126. {#if barColor != null && barFraction != null}
  127. <div style="width: {barFraction*100}%; background-color: rgb({barColor.red*255},{barColor.green*255},{barColor.blue*255})" class="flatboi2"></div>
  128. {/if}
  129. </div>
  130. </div>
  131. <style lang="sass">
  132. div.lamp
  133. cursor: pointer
  134. width: 19ch
  135. height: 2em
  136. margin: 0.5ch
  137. background: #18181c
  138. color: #84888f
  139. border-radius: 0.5ch
  140. border-top-right-radius: 1em
  141. overflow: hidden
  142. box-shadow: 1px 1px 1px #000
  143. @media screen and (max-width: 1921px)
  144. width: calc(20% - 1ch)
  145. @media screen and (max-width: 1600px)
  146. width: calc(25% - 1ch)
  147. @media screen and (max-width: 1200px)
  148. width: calc(33.3333333% - 1ch)
  149. @media screen and (max-width: 700px)
  150. width: calc(50% - 1ch)
  151. margin-top: 0.75ch
  152. margin-bottom: 0.75ch
  153. @media screen and (max-width: 380px)
  154. width: 100%
  155. &.selected
  156. background: #282833
  157. color: #cde
  158. > div.row
  159. display: flex
  160. > div.row-icon
  161. font-size: 1.8em
  162. position: absolute
  163. width: 0
  164. height: 0
  165. div.roundboi-text
  166. width: 0
  167. height: 0
  168. left: 1.4ch
  169. top: 0.9em
  170. text-align: center
  171. position: absolute
  172. font-size: 0.333em
  173. > div.title
  174. font-size: 0.9em
  175. text-align: left
  176. margin-top: 0.4em
  177. margin-left: 2em
  178. margin-right: 1ch
  179. height: 1.6em
  180. > div.name.hasRoleOrTag
  181. margin-top: -0.275em
  182. > div.tag-list
  183. display: flex
  184. flex-direction: row
  185. font-size: 0.5em
  186. > div.tag
  187. display: flex
  188. flex-direction: row
  189. padding: 0 0.5ch
  190. padding-right: 1ch
  191. div.tag-name
  192. padding-left: 0.5ch
  193. > div.flatboi
  194. height: 0.2em
  195. > div.flatboi2
  196. height: 0.2em
  197. &.compact
  198. width: 3.1ch
  199. border-top-right-radius: 0.25em
  200. margin: 0.25ch 0.25ch
  201. background: #282833
  202. &.selected
  203. background: #3c3c50
  204. div.row
  205. margin-left: -0.1ch
  206. div.title
  207. opacity: 0
  208. </style>