Browse Source
Version 0.2.0
Version 0.2.0
* Hide nanoleaf:IP devices in UI * Color clicking behavior is more clear * small icons for mega-groups * common tags/roles for groups * fix default names in UI state * hexagon corner * secondary lamp icon * fix selection bugs in device form * select all/none hotkey * quick assign modalbeelzebub 0.2.0
Gisle Aune
1 year ago
39 changed files with 873 additions and 47 deletions
-
3frontend/build/.gitignore
-
1frontend/build/_app/immutable/assets/2.8d52c342.css
-
1frontend/build/_app/immutable/assets/_page.b4e1e124.css
-
1frontend/build/_app/immutable/chunks/SelectContext.07ce997f.js
-
1frontend/build/_app/immutable/chunks/index.f09c780f.js
-
1frontend/build/_app/immutable/chunks/index.faf1c53d.js
-
1frontend/build/_app/immutable/chunks/singletons.6d605789.js
-
1frontend/build/_app/immutable/entry/app.16612d08.js
-
3frontend/build/_app/immutable/entry/start.08a3b3b3.js
-
1frontend/build/_app/immutable/nodes/0.202e929e.js
-
1frontend/build/_app/immutable/nodes/1.e0de7c8c.js
-
198frontend/build/_app/immutable/nodes/2.2ea37eba.js
-
1frontend/build/_app/version.json
-
BINfrontend/build/color-hsk.png
-
BINfrontend/build/color-xy.png
-
BINfrontend/build/favicon.png
-
68frontend/build/index.html
-
BINfrontend/icons.blend
-
29frontend/src/lib/components/ColorPicker.svelte
-
4frontend/src/lib/components/DeviceIcon.svelte
-
2frontend/src/lib/components/Icon.svelte
-
5frontend/src/lib/components/Lamp.svelte
-
67frontend/src/lib/components/MetaLamp.svelte
-
18frontend/src/lib/components/bforms/BFormColorOption.svelte
-
315frontend/src/lib/components/exetrnal/Range.svelte
-
13frontend/src/lib/components/icons/generic_lamp2.svg
-
10frontend/src/lib/components/icons/shape_hexagon_corner.svg
-
1frontend/src/lib/contexts/ModalContext.svelte
-
7frontend/src/lib/contexts/SelectContext.svelte
-
10frontend/src/lib/contexts/StateContext.svelte
-
11frontend/src/lib/modals/DeviceModal.svelte
-
94frontend/src/lib/modals/QuickAssignModal.svelte
-
1frontend/src/lib/modals/ScriptModal.svelte
-
19frontend/src/routes/+page.svelte
-
BINlucifer4-server
-
4services/nanoleaf/client.go
-
2services/nanoleaf/data.go
-
17services/uistate/data.go
-
3services/uistate/patch.go
@ -1,3 +0,0 @@ |
|||
/_app |
|||
*.png |
|||
*.html |
1
frontend/build/_app/immutable/assets/2.8d52c342.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1
frontend/build/_app/immutable/assets/_page.b4e1e124.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1
frontend/build/_app/immutable/chunks/SelectContext.07ce997f.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1
frontend/build/_app/immutable/chunks/index.f09c780f.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1 @@ |
|||
import{H as f,s as y,a8 as m,Z as q,a9 as w}from"./index.f09c780f.js";const o=[];function z(n,i){return{subscribe:A(n,i).subscribe}}function A(n,i=f){let u;const t=new Set;function a(e){if(y(n,e)&&(n=e,u)){const r=!o.length;for(const s of t)s[1](),o.push(s,n);if(r){for(let s=0;s<o.length;s+=2)o[s][0](o[s+1]);o.length=0}}}function l(e){a(e(n))}function b(e,r=f){const s=[e,r];return t.add(s),t.size===1&&(u=i(a)||f),e(n),()=>{t.delete(s),t.size===0&&(u(),u=null)}}return{set:a,update:l,subscribe:b}}function H(n,i,u){const t=!Array.isArray(n),a=t?[n]:n,l=i.length<2;return z(u,b=>{let e=!1;const r=[];let s=0,d=f;const g=()=>{if(s)return;d();const c=i(t?r[0]:r,b);l?b(c):d=w(c)?c:f},_=a.map((c,p)=>m(c,h=>{r[p]=h,s&=~(1<<p),e&&g()},()=>{s|=1<<p}));return e=!0,g(),function(){q(_),d()}})}export{H as d,A as w}; |
@ -0,0 +1 @@ |
|||
import{w as u}from"./index.faf1c53d.js";var _;const v=((_=globalThis.__sveltekit_1k9l1nc)==null?void 0:_.base)??"";var g;const k=((g=globalThis.__sveltekit_1k9l1nc)==null?void 0:g.assets)??v,m="1697835102898",R="sveltekit:snapshot",T="sveltekit:scroll",y="sveltekit:index",f={tap:1,hover:2,viewport:3,eager:4,off:-1};function I(e){let t=e.baseURI;if(!t){const n=e.getElementsByTagName("base");t=n.length?n[0].href:e.URL}return t}function S(){return{x:pageXOffset,y:pageYOffset}}function c(e,t){return e.getAttribute(`data-sveltekit-${t}`)}const d={...f,"":f.hover};function h(e){let t=e.assignedSlot??e.parentNode;return(t==null?void 0:t.nodeType)===11&&(t=t.host),t}function x(e,t){for(;e&&e!==t;){if(e.nodeName.toUpperCase()==="A"&&e.hasAttribute("href"))return e;e=h(e)}}function O(e,t){let n;try{n=new URL(e instanceof SVGAElement?e.href.baseVal:e.href,document.baseURI)}catch{}const o=e instanceof SVGAElement?e.target.baseVal:e.target,l=!n||!!o||E(n,t)||(e.getAttribute("rel")||"").split(/\s+/).includes("external"),r=(n==null?void 0:n.origin)===location.origin&&e.hasAttribute("download");return{url:n,external:l,target:o,download:r}}function U(e){let t=null,n=null,o=null,l=null,r=null,a=null,s=e;for(;s&&s!==document.documentElement;)o===null&&(o=c(s,"preload-code")),l===null&&(l=c(s,"preload-data")),t===null&&(t=c(s,"keepfocus")),n===null&&(n=c(s,"noscroll")),r===null&&(r=c(s,"reload")),a===null&&(a=c(s,"replacestate")),s=h(s);function i(b){switch(b){case"":case"true":return!0;case"off":case"false":return!1;default:return null}}return{preload_code:d[o??"off"],preload_data:d[l??"off"],keep_focus:i(t),noscroll:i(n),reload:i(r),replace_state:i(a)}}function p(e){const t=u(e);let n=!0;function o(){n=!0,t.update(a=>a)}function l(a){n=!1,t.set(a)}function r(a){let s;return t.subscribe(i=>{(s===void 0||n&&i!==s)&&a(s=i)})}return{notify:o,set:l,subscribe:r}}function w(){const{set:e,subscribe:t}=u(!1);let n;async function o(){clearTimeout(n);try{const l=await fetch(`${k}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!l.ok)return!1;const a=(await l.json()).version!==m;return a&&(e(!0),clearTimeout(n)),a}catch{return!1}}return{subscribe:t,check:o}}function E(e,t){return e.origin!==location.origin||!e.pathname.startsWith(t)}function L(e){e.client}const N={url:p({}),page:p({}),navigating:u(null),updated:w()};export{y as I,f as P,T as S,R as a,O as b,U as c,N as d,v as e,x as f,I as g,L as h,E as i,S as s}; |
1
frontend/build/_app/immutable/entry/app.16612d08.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
3
frontend/build/_app/immutable/entry/start.08a3b3b3.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1 @@ |
|||
import{S as i,i as _,s as m,y as c,z as $,A as f,g as r,d as l,B as u,C as p,D as g,E as d,F as S}from"../chunks/index.f09c780f.js";import{S as x,a as h,M as C}from"../chunks/SelectContext.07ce997f.js";const b=!0,q=Object.freeze(Object.defineProperty({__proto__:null,prerender:b},Symbol.toStringTag,{value:"Module"}));function v(a){let t;const o=a[0].default,e=p(o,a,a[1],null);return{c(){e&&e.c()},l(n){e&&e.l(n)},m(n,s){e&&e.m(n,s),t=!0},p(n,s){e&&e.p&&(!t||s&2)&&g(e,o,n,n[1],t?S(o,n[1],s,null):d(n[1]),null)},i(n){t||(r(e,n),t=!0)},o(n){l(e,n),t=!1},d(n){e&&e.d(n)}}}function w(a){let t,o;return t=new C({props:{$$slots:{default:[v]},$$scope:{ctx:a}}}),{c(){c(t.$$.fragment)},l(e){$(t.$$.fragment,e)},m(e,n){f(t,e,n),o=!0},p(e,n){const s={};n&2&&(s.$$scope={dirty:n,ctx:e}),t.$set(s)},i(e){o||(r(t.$$.fragment,e),o=!0)},o(e){l(t.$$.fragment,e),o=!1},d(e){u(t,e)}}}function y(a){let t,o;return t=new h({props:{$$slots:{default:[w]},$$scope:{ctx:a}}}),{c(){c(t.$$.fragment)},l(e){$(t.$$.fragment,e)},m(e,n){f(t,e,n),o=!0},p(e,n){const s={};n&2&&(s.$$scope={dirty:n,ctx:e}),t.$set(s)},i(e){o||(r(t.$$.fragment,e),o=!0)},o(e){l(t.$$.fragment,e),o=!1},d(e){u(t,e)}}}function M(a){let t,o;return t=new x({props:{$$slots:{default:[y]},$$scope:{ctx:a}}}),{c(){c(t.$$.fragment)},l(e){$(t.$$.fragment,e)},m(e,n){f(t,e,n),o=!0},p(e,[n]){const s={};n&2&&(s.$$scope={dirty:n,ctx:e}),t.$set(s)},i(e){o||(r(t.$$.fragment,e),o=!0)},o(e){l(t.$$.fragment,e),o=!1},d(e){u(t,e)}}}function j(a,t,o){let{$$slots:e={},$$scope:n}=t;return a.$$set=s=>{"$$scope"in s&&o(1,n=s.$$scope)},[e,n]}class A extends i{constructor(t){super(),_(this,t,j,M,m,{})}}export{A as component,q as universal}; |
@ -0,0 +1 @@ |
|||
import{S,i as q,s as x,k as _,q as f,a as H,l as d,m as g,r as h,h as u,c as k,b as m,G as v,u as $,H as E,I as y}from"../chunks/index.f09c780f.js";import{d as C}from"../chunks/singletons.6d605789.js";const G=()=>{const s=C;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},I={subscribe(s){return G().page.subscribe(s)}};function P(s){var b;let t,r=s[0].status+"",o,n,i,c=((b=s[0].error)==null?void 0:b.message)+"",l;return{c(){t=_("h1"),o=f(r),n=H(),i=_("p"),l=f(c)},l(e){t=d(e,"H1",{});var a=g(t);o=h(a,r),a.forEach(u),n=k(e),i=d(e,"P",{});var p=g(i);l=h(p,c),p.forEach(u)},m(e,a){m(e,t,a),v(t,o),m(e,n,a),m(e,i,a),v(i,l)},p(e,[a]){var p;a&1&&r!==(r=e[0].status+"")&&$(o,r),a&1&&c!==(c=((p=e[0].error)==null?void 0:p.message)+"")&&$(l,c)},i:E,o:E,d(e){e&&u(t),e&&u(n),e&&u(i)}}}function j(s,t,r){let o;return y(s,I,n=>r(0,o=n)),[o]}let A=class extends S{constructor(t){super(),q(this,t,j,P,x,{})}};export{A as component}; |
198
frontend/build/_app/immutable/nodes/2.2ea37eba.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1 @@ |
|||
{"version":"1697835102898"} |
After Width: 1000 | Height: 1000 | Size: 173 KiB |
After Width: 500 | Height: 500 | Size: 45 KiB |
After Width: 128 | Height: 128 | Size: 1.5 KiB |
@ -0,0 +1,68 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<link rel="icon" href="./favicon.png" /> |
|||
<meta name="viewport" content="width=device-width" /> |
|||
<meta name="darkreader" content="we've already gone dark" /> |
|||
<style> |
|||
body, html { |
|||
background: #111114; |
|||
color: #ccc; |
|||
margin: 0; |
|||
padding: 0; |
|||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|||
} |
|||
</style> |
|||
|
|||
<link href="./_app/immutable/assets/2.8d52c342.css" rel="stylesheet"> |
|||
<link rel="modulepreload" href="./_app/immutable/entry/start.08a3b3b3.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/chunks/index.f09c780f.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/chunks/singletons.6d605789.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/chunks/index.faf1c53d.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/entry/app.16612d08.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/nodes/0.202e929e.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/chunks/SelectContext.07ce997f.js"> |
|||
<link rel="modulepreload" href="./_app/immutable/nodes/2.2ea37eba.js"> |
|||
</head> |
|||
<body data-sveltekit-preload-data="hover"> |
|||
<div style="display: contents"> |
|||
|
|||
|
|||
|
|||
|
|||
<div class="page svelte-170wh9b"></div> |
|||
|
|||
<form novalidate></form> |
|||
<form novalidate></form> |
|||
<form novalidate></form> |
|||
|
|||
|
|||
|
|||
<script> |
|||
{ |
|||
__sveltekit_1k9l1nc = { |
|||
base: new URL(".", location).pathname.slice(0, -1), |
|||
env: {} |
|||
}; |
|||
|
|||
const element = document.currentScript.parentElement; |
|||
|
|||
const data = [null,null]; |
|||
|
|||
Promise.all([ |
|||
import("./_app/immutable/entry/start.08a3b3b3.js"), |
|||
import("./_app/immutable/entry/app.16612d08.js") |
|||
]).then(([kit, app]) => { |
|||
kit.start(app, element, { |
|||
node_ids: [0, 2], |
|||
data, |
|||
form: null, |
|||
error: null |
|||
}); |
|||
}); |
|||
} |
|||
</script> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,315 @@ |
|||
<script lang="js"> |
|||
import { createEventDispatcher } from "svelte"; |
|||
import { fly, fade } from "svelte/transition"; |
|||
|
|||
// Props |
|||
export let min = 0; |
|||
export let max = 100; |
|||
export let initialValue = 0; |
|||
export let id = null; |
|||
export let value = |
|||
typeof initialValue === "string" ? parseInt(initialValue) : initialValue; |
|||
|
|||
// Node Bindings |
|||
let container = null; |
|||
let thumb = null; |
|||
let progressBar = null; |
|||
let element = null; |
|||
|
|||
// Internal State |
|||
let elementX = null; |
|||
let currentThumb = null; |
|||
let holding = false; |
|||
let thumbHover = false; |
|||
let keydownAcceleration = 0; |
|||
let accelerationTimer = null; |
|||
|
|||
// Dispatch 'change' events |
|||
const dispatch = createEventDispatcher(); |
|||
|
|||
// Mouse shield used onMouseDown to prevent any mouse events penetrating other elements, |
|||
// ie. hover events on other elements while dragging. Especially for Safari |
|||
const mouseEventShield = document.createElement("div"); |
|||
mouseEventShield.setAttribute("class", "mouse-over-shield"); |
|||
mouseEventShield.addEventListener("mouseover", (e) => { |
|||
e.preventDefault(); |
|||
e.stopPropagation(); |
|||
}); |
|||
|
|||
function resizeWindow() { |
|||
elementX = element.getBoundingClientRect().left; |
|||
} |
|||
|
|||
// Allows both bind:value and on:change for parent value retrieval |
|||
function setValue(val) { |
|||
value = val; |
|||
dispatch("change", { value }); |
|||
} |
|||
|
|||
function onTrackEvent(e) { |
|||
// Update value immediately before beginning drag |
|||
updateValueOnEvent(e); |
|||
onDragStart(e); |
|||
} |
|||
|
|||
function onHover(e) { |
|||
thumbHover = thumbHover ? false : true; |
|||
} |
|||
|
|||
function onDragStart(e) { |
|||
// If mouse event add a pointer events shield |
|||
if (e.type === "mousedown") document.body.append(mouseEventShield); |
|||
currentThumb = thumb; |
|||
} |
|||
|
|||
function onDragEnd(e) { |
|||
// If using mouse - remove pointer event shield |
|||
if (e.type === "mouseup") { |
|||
if (document.body.contains(mouseEventShield)) |
|||
document.body.removeChild(mouseEventShield); |
|||
// Needed to check whether thumb and mouse overlap after shield removed |
|||
if (isMouseInElement(e, thumb)) thumbHover = true; |
|||
} |
|||
currentThumb = null; |
|||
} |
|||
|
|||
// Check if mouse event cords overlay with an element's area |
|||
function isMouseInElement(event, element) { |
|||
let rect = element.getBoundingClientRect(); |
|||
let { clientX: x, clientY: y } = event; |
|||
if (x < rect.left || x >= rect.right) return false; |
|||
if (y < rect.top || y >= rect.bottom) return false; |
|||
return true; |
|||
} |
|||
|
|||
// Accessible keypress handling |
|||
function onKeyPress(e) { |
|||
// Max out at +/- 10 to value per event (50 events / 5) |
|||
// 100 below is to increase the amount of events required to reach max velocity |
|||
if (keydownAcceleration < 50) keydownAcceleration++; |
|||
let throttled = Math.ceil(keydownAcceleration / 5); |
|||
|
|||
if (e.key === "ArrowUp" || e.key === "ArrowRight") { |
|||
if (value + throttled > max || value >= max) { |
|||
setValue(max); |
|||
} else { |
|||
setValue(value + throttled); |
|||
} |
|||
} |
|||
if (e.key === "ArrowDown" || e.key === "ArrowLeft") { |
|||
if (value - throttled < min || value <= min) { |
|||
setValue(min); |
|||
} else { |
|||
setValue(value - throttled); |
|||
} |
|||
} |
|||
|
|||
// Reset acceleration after 100ms of no events |
|||
clearTimeout(accelerationTimer); |
|||
accelerationTimer = setTimeout(() => (keydownAcceleration = 1), 100); |
|||
} |
|||
|
|||
function calculateNewValue(clientX) { |
|||
// Find distance between cursor and element's left cord (20px / 2 = 10px) - Center of thumb |
|||
let delta = clientX - (elementX + 10); |
|||
|
|||
// Use width of the container minus (5px * 2 sides) offset for percent calc |
|||
let percent = (delta * 100) / (container.clientWidth - 10); |
|||
|
|||
// Limit percent 0 -> 100 |
|||
percent = percent < 0 ? 0 : percent > 100 ? 100 : percent; |
|||
|
|||
// Limit value min -> max |
|||
setValue(parseInt((percent * (max - min)) / 100) + min); |
|||
} |
|||
|
|||
// Handles both dragging of touch/mouse as well as simple one-off click/touches |
|||
function updateValueOnEvent(e) { |
|||
// touchstart && mousedown are one-off updates, otherwise expect a currentPointer node |
|||
if (!currentThumb && e.type !== "touchstart" && e.type !== "mousedown") |
|||
return false; |
|||
|
|||
if (e.stopPropagation) e.stopPropagation(); |
|||
if (e.preventDefault) e.preventDefault(); |
|||
|
|||
// Get client's x cord either touch or mouse |
|||
const clientX = |
|||
e.type === "touchmove" || e.type === "touchstart" |
|||
? e.touches[0].clientX |
|||
: e.clientX; |
|||
|
|||
calculateNewValue(clientX); |
|||
} |
|||
|
|||
// React to left position of element relative to window |
|||
$: if (element) elementX = element.getBoundingClientRect().left; |
|||
|
|||
// Set a class based on if dragging |
|||
$: holding = Boolean(currentThumb); |
|||
|
|||
// Update progressbar and thumb styles to represent value |
|||
$: if (progressBar && thumb) { |
|||
// Limit value min -> max |
|||
value = value > min ? value : min; |
|||
value = value < max ? value : max; |
|||
|
|||
let percent = ((value - min) * 100) / (max - min); |
|||
let offsetLeft = (container.clientWidth - 10) * (percent / 100) + 5; |
|||
|
|||
// Update thumb position + active range track width |
|||
thumb.style.left = `${offsetLeft}px`; |
|||
progressBar.style.width = `${offsetLeft}px`; |
|||
} |
|||
</script> |
|||
|
|||
<svelte:window |
|||
on:touchmove|nonpassive={updateValueOnEvent} |
|||
on:touchcancel={onDragEnd} |
|||
on:touchend={onDragEnd} |
|||
on:mousemove={updateValueOnEvent} |
|||
on:mouseup={onDragEnd} |
|||
on:resize={resizeWindow} |
|||
/> |
|||
<div class="range"> |
|||
<div |
|||
class="range__wrapper" |
|||
tabindex="0" |
|||
on:keydown={onKeyPress} |
|||
bind:this={element} |
|||
role="slider" |
|||
aria-valuemin={min} |
|||
aria-valuemax={max} |
|||
aria-valuenow={value} |
|||
{id} |
|||
on:mousedown={onTrackEvent} |
|||
on:touchstart={onTrackEvent} |
|||
> |
|||
<div class="range__track" bind:this={container}> |
|||
<div class="range__track--highlighted" bind:this={progressBar} /> |
|||
<div |
|||
class="range__thumb" |
|||
class:range__thumb--holding={holding} |
|||
bind:this={thumb} |
|||
on:touchstart={onDragStart} |
|||
on:mousedown={onDragStart} |
|||
on:mouseover={() => (thumbHover = true)} |
|||
on:mouseout={() => (thumbHover = false)} |
|||
> |
|||
{#if holding || thumbHover} |
|||
<div |
|||
class="range__tooltip" |
|||
in:fly={{ y: 7, duration: 200 }} |
|||
out:fade={{ duration: 100 }} |
|||
> |
|||
{value} |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<svelte:head> |
|||
<style> |
|||
.mouse-over-shield { |
|||
position: fixed; |
|||
top: 0px; |
|||
left: 0px; |
|||
height: 100%; |
|||
width: 100%; |
|||
background-color: rgba(255, 0, 0, 0); |
|||
z-index: 10000; |
|||
cursor: grabbing; |
|||
} |
|||
</style> |
|||
</svelte:head> |
|||
|
|||
<style> |
|||
.range { |
|||
position: relative; |
|||
flex: 1; |
|||
} |
|||
|
|||
.range__wrapper { |
|||
min-width: 100%; |
|||
position: relative; |
|||
padding: 0.5rem; |
|||
box-sizing: border-box; |
|||
outline: none; |
|||
} |
|||
|
|||
.range__wrapper:focus-visible > .range__track { |
|||
box-shadow: 0 0 0 2px white, 0 0 0 3px var(--track-focus, #6185ff); |
|||
} |
|||
|
|||
.range__track { |
|||
height: 6px; |
|||
background-color: var(--track-bgcolor, #d0d0d0); |
|||
border-radius: 999px; |
|||
} |
|||
|
|||
.range__track--highlighted { |
|||
background-color: var(--track-highlight-bgcolor, #6185ff); |
|||
background: var( |
|||
--track-highlight-bg, |
|||
linear-gradient(90deg, #6185ff, #9c65ff) |
|||
); |
|||
width: 0; |
|||
height: 6px; |
|||
position: absolute; |
|||
border-radius: 999px; |
|||
} |
|||
|
|||
.range__thumb { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
position: absolute; |
|||
width: 20px; |
|||
height: 20px; |
|||
background-color: var(--thumb-bgcolor, white); |
|||
cursor: pointer; |
|||
border-radius: 999px; |
|||
margin-top: -8px; |
|||
transition: box-shadow 100ms; |
|||
user-select: none; |
|||
box-shadow: var( |
|||
--thumb-boxshadow, |
|||
0 1px 1px 0 rgba(0, 0, 0, 0.14), |
|||
0 0px 2px 1px rgba(0, 0, 0, 0.2) |
|||
); |
|||
} |
|||
|
|||
.range__thumb--holding { |
|||
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.14), |
|||
0 1px 2px 1px rgba(0, 0, 0, 0.2), |
|||
0 0 0 6px var(--thumb-holding-outline, rgba(113, 119, 250, 0.3)); |
|||
} |
|||
|
|||
.range__tooltip { |
|||
pointer-events: none; |
|||
position: absolute; |
|||
top: -33px; |
|||
color: var(--tooltip-text, white); |
|||
width: 38px; |
|||
padding: 4px 0; |
|||
border-radius: 4px; |
|||
text-align: center; |
|||
background-color: var(--tooltip-bgcolor, #6185ff); |
|||
background: var(--tooltip-bg, linear-gradient(45deg, #6185ff, #9c65ff)); |
|||
} |
|||
|
|||
.range__tooltip::after { |
|||
content: ""; |
|||
display: block; |
|||
position: absolute; |
|||
height: 7px; |
|||
width: 7px; |
|||
background-color: var(--tooltip-bgcolor, #6185ff); |
|||
bottom: -3px; |
|||
left: calc(50% - 3px); |
|||
clip-path: polygon(0% 0%, 100% 100%, 0% 100%); |
|||
transform: rotate(-45deg); |
|||
border-radius: 0 0 0 3px; |
|||
} |
|||
</style> |
@ -0,0 +1,13 @@ |
|||
<?xml version='1.0' encoding='ascii'?> |
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320"> |
|||
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet"> |
|||
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes"> |
|||
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 200.384, 268.295 198.051, 264.451 194.264, 260.908 189.167, 257.803 182.957, 255.255 175.872, 253.361 168.184, 252.195 160.188, 251.801 152.193, 252.195 144.505, 253.361 137.419, 255.255 131.209, 257.803 126.112, 260.908 122.325, 264.451 119.993, 268.295 119.205, 272.293 119.205, 278.205 119.993, 282.203 122.325, 286.047 126.112, 289.589 131.209, 292.695 137.419, 295.243 144.505, 297.137 152.193, 298.303 160.188, 298.696 168.184, 298.303 175.872, 297.137 182.957, 295.243 189.167, 292.695 194.264, 289.589 198.051, 286.047 200.384, 282.203 201.171, 278.205 " /> |
|||
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 119.205, 272.293 119.993, 276.291 122.325, 280.135 126.112, 283.677 131.209, 286.783 137.419, 289.331 144.505, 291.224 152.193, 292.391 160.188, 292.784 168.184, 292.391 175.872, 291.224 182.957, 289.331 189.167, 286.783 194.264, 283.677 198.051, 280.135 200.384, 276.291 200.589, 275.249 201.171, 272.293 200.384, 268.295 " /> |
|||
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 201.171, 278.205 201.171, 272.293 " /> |
|||
<path style="fill:#0000ff;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 153.947, 179.170 153.947, 189.170 153.947, 199.170 153.947, 209.170 153.947, 219.170 153.947, 229.170 153.947, 239.170 153.947, 249.170 153.947, 252.109 153.947, 262.109 153.947, 270.606 154.309, 271.203 154.897, 271.753 155.689, 272.235 156.653, 272.631 157.753, 272.925 158.947, 273.106 160.188, 273.167 161.430, 273.106 162.623, 272.925 163.723, 272.631 164.688, 272.235 165.479, 271.753 166.067, 271.203 166.429, 270.606 166.429, 260.606 166.429, 252.109 166.429, 242.109 166.429, 232.109 166.429, 222.109 166.429, 212.109 166.429, 202.109 166.429, 192.109 166.429, 182.109 166.429, 179.170 " /> |
|||
<path style="fill:#ff0000;fill-opacity:0.85" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 88.941, 155.674 96.067, 162.340 104.607, 167.543 105.658, 168.183 114.909, 171.979 117.344, 172.979 127.005, 175.560 130.677, 176.542 140.563, 178.041 145.143, 178.736 153.947, 179.170 160.188, 179.477 166.429, 179.170 175.233, 178.736 185.120, 177.236 189.700, 176.542 199.361, 173.960 203.032, 172.979 212.284, 169.182 214.718, 168.183 223.258, 162.980 224.309, 162.340 231.435, 155.674 235.824, 148.441 237.306, 140.918 233.154, 131.821 229.002, 122.723 224.850, 113.626 220.698, 104.529 216.546, 95.431 212.394, 86.334 208.242, 77.237 204.090, 68.139 199.938, 59.042 195.786, 49.945 191.635, 40.847 189.441, 36.041 188.879, 38.895 187.214, 41.638 184.511, 44.167 180.873, 46.384 176.440, 48.203 171.383, 49.554 165.895, 50.387 160.188, 50.668 154.481, 50.387 148.994, 49.554 143.936, 48.203 139.503, 46.384 135.865, 44.167 133.162, 41.638 131.497, 38.895 130.935, 36.041 126.783, 45.139 122.631, 54.236 118.479, 63.333 114.328, 72.431 110.176, 81.528 106.024, 90.625 101.872, 99.723 97.720, 108.820 93.568, 117.917 89.416, 127.015 85.264, 136.112 83.071, 140.918 84.552, 148.441 88.941, 155.674 " /> |
|||
<path style="fill:#ff0000;fill-opacity:1" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 160.188, 21.446 154.493, 21.727 149.018, 22.557 143.971, 23.906 139.548, 25.721 135.917, 27.933 133.220, 30.456 131.559, 33.194 130.998, 36.041 131.559, 38.889 133.220, 41.626 135.917, 44.150 139.548, 46.361 143.971, 48.177 149.018, 49.525 154.493, 50.356 160.188, 50.636 165.883, 50.356 171.359, 49.525 176.405, 48.177 180.829, 46.361 184.459, 44.150 187.156, 41.626 188.817, 38.889 189.378, 36.041 188.817, 33.194 187.156, 30.456 184.459, 27.933 180.829, 25.721 176.405, 23.906 171.359, 22.557 165.883, 21.727 160.188, 21.446 " /> |
|||
</g> |
|||
</g> |
|||
</svg> |
@ -0,0 +1,10 @@ |
|||
<?xml version='1.0' encoding='ascii'?> |
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="320" height="320"> |
|||
<g id="View Layer_LineSet" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="View Layer_LineSet"> |
|||
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes"> |
|||
<path style="fill:#ff0000;fill-opacity:0.333" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 201.919, 108.039 198.923, 99.664 195.119, 91.623 193.302, 88.591 184.641, 93.591 175.981, 98.591 170.168, 101.947 171.536, 104.229 174.217, 109.898 176.329, 115.802 177.853, 121.885 178.773, 128.087 179.081, 134.351 178.773, 140.614 177.853, 146.817 176.329, 152.899 174.217, 158.803 171.536, 164.472 168.312, 169.851 164.577, 174.887 160.365, 179.534 155.719, 183.745 150.682, 187.480 145.304, 190.704 139.635, 193.385 133.731, 195.498 127.648, 197.021 121.445, 197.941 115.182, 198.249 114.613, 198.221 114.613, 208.221 114.613, 218.221 114.613, 224.962 115.182, 224.990 124.067, 224.554 132.865, 223.249 141.494, 221.087 149.869, 218.091 157.910, 214.288 165.539, 209.715 172.684, 204.416 179.274, 198.443 185.248, 191.852 190.546, 184.707 195.119, 177.078 198.923, 169.037 201.919, 160.662 204.080, 152.034 205.386, 143.235 205.822, 134.351 205.386, 125.466 204.080, 116.668 201.919, 108.039 " /> |
|||
<path style="fill:#ff0000;fill-opacity:0.666" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 139.635, 193.385 145.304, 190.704 150.682, 187.480 155.719, 183.745 160.365, 179.534 164.577, 174.887 168.312, 169.851 171.536, 164.472 174.217, 158.803 176.329, 152.899 177.853, 146.817 178.773, 140.614 179.081, 134.351 178.773, 128.087 177.853, 121.885 176.329, 115.802 174.217, 109.898 171.536, 104.229 170.168, 101.947 161.508, 106.947 152.847, 111.947 149.399, 113.938 150.363, 115.546 152.037, 119.085 153.356, 122.771 154.307, 126.568 154.881, 130.441 155.073, 134.351 154.881, 138.261 154.307, 142.133 153.356, 145.930 152.037, 149.616 150.363, 153.155 148.350, 156.513 146.018, 159.657 143.389, 162.558 140.489, 165.187 137.345, 167.519 133.987, 169.531 130.448, 171.205 126.762, 172.524 122.965, 173.475 119.092, 174.049 115.182, 174.242 114.613, 174.214 114.613, 184.214 114.613, 194.214 114.613, 198.221 115.182, 198.249 121.445, 197.941 127.648, 197.021 133.731, 195.498 139.635, 193.385 " /> |
|||
<path style="fill:#ff0000;fill-opacity:1.0" fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 0)" stroke-linejoin="miter" d=" M 114.879, 134.338 114.765, 134.591 114.681, 134.848 114.630, 135.099 114.613, 135.337 114.613, 145.337 114.613, 155.337 114.613, 165.337 114.613, 174.214 115.182, 174.242 119.092, 174.049 122.965, 173.475 126.762, 172.524 130.448, 171.205 133.987, 169.531 137.345, 167.519 140.489, 165.187 143.389, 162.558 146.018, 159.657 148.350, 156.513 150.363, 153.155 152.037, 149.616 153.356, 145.930 154.307, 142.133 154.881, 138.261 155.073, 134.351 154.881, 130.441 154.307, 126.568 153.356, 122.771 152.037, 119.085 150.363, 115.546 149.399, 113.938 140.739, 118.938 132.078, 123.938 123.418, 128.938 115.752, 133.364 115.554, 133.498 115.362, 133.668 115.182, 133.869 115.020, 134.095 114.879, 134.338 " /> |
|||
</g> |
|||
</g> |
|||
</svg> |
@ -0,0 +1,94 @@ |
|||
<script lang="ts"> |
|||
import { runCommand } from "$lib/client/lucifer"; |
|||
import ColorPicker from "$lib/components/ColorPicker.svelte"; |
|||
import Modal from "$lib/components/Modal.svelte"; |
|||
import ModalBody from "$lib/components/ModalBody.svelte"; |
|||
import Range from "$lib/components/exetrnal/Range.svelte"; |
|||
import { getModalContext } from "$lib/contexts/ModalContext.svelte"; |
|||
import { getSelectedContext } from "$lib/contexts/SelectContext.svelte"; |
|||
import { getStateContext } from "$lib/contexts/StateContext.svelte"; |
|||
import { toEffectRaw } from "$lib/models/assignment"; |
|||
import { parseColor, type Color, stringifyColor } from "$lib/models/color"; |
|||
|
|||
const { modal } = getModalContext(); |
|||
const { selectedMasks, selectedMap } = getSelectedContext(); |
|||
const { assignmentList } = getStateContext(); |
|||
|
|||
let show: boolean = false; |
|||
let color: Color = { k: 2750 }; |
|||
let intensity: number = 50; |
|||
let match: string = ""; |
|||
let disabled: boolean = false; |
|||
|
|||
async function submitForm() { |
|||
disabled = true; |
|||
|
|||
try { |
|||
await runCommand({assign: { match, effect: { solid: { |
|||
interleave: 0, |
|||
animationMs: 0, |
|||
states: [ { color: stringifyColor(color)!, intensity: (intensity / 100), power: intensity > 0, temperature: null } ] |
|||
} } }}); |
|||
} catch (err) {} |
|||
|
|||
disabled = false; |
|||
} |
|||
|
|||
function initForm() { |
|||
show = true; |
|||
let mostPopularEffect = 0; |
|||
for (const assignment of $assignmentList) { |
|||
const selectedCount = assignment.deviceIds?.filter(id => $selectedMap[id]).length || 0; |
|||
if (selectedCount > mostPopularEffect) { |
|||
color = parseColor(toEffectRaw(assignment.effect).states.find(s => s.color)?.color || "k:2750") || { k: 2750 }; |
|||
intensity = (toEffectRaw(assignment.effect).states.find(s => s.intensity)?.intensity || 0.5) * 100; |
|||
|
|||
mostPopularEffect = selectedCount; |
|||
} |
|||
} |
|||
} |
|||
|
|||
$: if ($modal.kind === "device.quickassign") { |
|||
initForm() |
|||
} else { |
|||
show = false; |
|||
} |
|||
|
|||
$: if (!$selectedMasks.includes(match)) { |
|||
match = $selectedMasks[0]; |
|||
} |
|||
</script> |
|||
|
|||
<form novalidate on:submit|preventDefault={submitForm}> |
|||
<Modal |
|||
closable {show} {disabled} |
|||
titleText="Quick Assign" |
|||
submitText="Apply" |
|||
> |
|||
<ModalBody> |
|||
<label for="mask">Selection</label> |
|||
<select bind:value={match}> |
|||
{#each $selectedMasks as option (option)} |
|||
<option value={option}>{option}</option> |
|||
{/each} |
|||
</select> |
|||
<div class="color-name">{stringifyColor(color)}</div> |
|||
<div class="color-wrapper"> |
|||
<ColorPicker bind:color={color} /> |
|||
</div> |
|||
<Range bind:value={intensity} /> |
|||
</ModalBody> |
|||
</Modal> |
|||
</form> |
|||
|
|||
<style lang="sass"> |
|||
div.color-wrapper |
|||
padding-top: 0.2em |
|||
margin-bottom: -0.5em |
|||
font-size: 3.6em |
|||
overflow: hidden |
|||
|
|||
div.color-name |
|||
font-size: 1.5em |
|||
text-align: center |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue