room groups and grouping.
This commit is contained in:
@@ -136,9 +136,23 @@
|
|||||||
overflow: hidden
|
overflow: hidden
|
||||||
box-shadow: 1px 1px 1px #000
|
box-shadow: 1px 1px 1px #000
|
||||||
|
|
||||||
|
@media screen and (max-width: 1921px)
|
||||||
|
width: calc(20% - 1ch)
|
||||||
|
|
||||||
|
@media screen and (max-width: 1600px)
|
||||||
|
width: calc(25% - 1ch)
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px)
|
||||||
|
width: calc(33.3333333% - 1ch)
|
||||||
|
|
||||||
@media screen and (max-width: 700px)
|
@media screen and (max-width: 700px)
|
||||||
width: 100%
|
width: calc(50% - 1ch)
|
||||||
|
margin-top: 0.75ch
|
||||||
|
margin-bottom: 0.75ch
|
||||||
|
|
||||||
|
@media screen and (max-width: 380px)
|
||||||
|
width: 100%
|
||||||
|
|
||||||
&.selected
|
&.selected
|
||||||
background: #282833
|
background: #282833
|
||||||
color: #cde
|
color: #cde
|
||||||
@@ -176,8 +190,13 @@
|
|||||||
height: 0.2em
|
height: 0.2em
|
||||||
|
|
||||||
&.compact
|
&.compact
|
||||||
width: 3ch
|
width: 3.1ch
|
||||||
border-top-right-radius: 0.25em
|
border-top-right-radius: 0.25em
|
||||||
|
margin: 0.25ch 0.25ch
|
||||||
|
background: #282833
|
||||||
|
|
||||||
|
&.selected
|
||||||
|
background: #3c3c50
|
||||||
div.row
|
div.row
|
||||||
margin-left: -0.1ch
|
margin-left: -0.1ch
|
||||||
div.title
|
div.title
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export const DARK_COLOR = rgb(0.5176470588235295/2, 0.5333333333333333/2, 0.5607843137254902/1.75);
|
||||||
|
export const DARK_COLOR_SELECTED = rgb(0.5176470588235295/1.5, 0.5333333333333333/1.5, 0.5607843137254902/1.25);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
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";
|
||||||
|
import DeviceIcon from "./DeviceIcon.svelte";
|
||||||
|
import Lamp from "./Lamp.svelte";
|
||||||
|
|
||||||
|
export let name: string
|
||||||
|
export let devices: Device[];
|
||||||
|
|
||||||
|
let selected: boolean;
|
||||||
|
|
||||||
|
const {selectedMap, toggleMultiSelection} = getSelectedContext();
|
||||||
|
|
||||||
|
function onSelect() {
|
||||||
|
toggleMultiSelection(devices.map(d => d.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$: selected = !devices.find(d => !$selectedMap[d.id])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="metalamp" class:selected>
|
||||||
|
<div class="row">
|
||||||
|
<div class="title" on:click={onSelect}>{name}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{#each devices as device (device.id)}
|
||||||
|
<Lamp compact device={device} />
|
||||||
|
{/each}
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
<div style="width: 3.6ch"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
div.metalamp
|
||||||
|
user-select: none
|
||||||
|
cursor: pointer
|
||||||
|
width: 19ch
|
||||||
|
margin: 0.5ch
|
||||||
|
background: #18181c
|
||||||
|
color: #84888f
|
||||||
|
border-radius: 0.5ch
|
||||||
|
border-top-right-radius: 1em
|
||||||
|
overflow: hidden
|
||||||
|
box-shadow: 1px 1px 1px #000
|
||||||
|
padding-bottom: 0.5ch
|
||||||
|
|
||||||
|
@media screen and (max-width: 1921px)
|
||||||
|
width: calc(20% - 1ch)
|
||||||
|
|
||||||
|
@media screen and (max-width: 1600px)
|
||||||
|
width: calc(25% - 1ch)
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px)
|
||||||
|
width: calc(33.3333333% - 1ch)
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px)
|
||||||
|
width: calc(50% - 1ch)
|
||||||
|
margin-top: 0.75ch
|
||||||
|
margin-bottom: 0.75ch
|
||||||
|
|
||||||
|
@media screen and (max-width: 380px)
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
&.selected
|
||||||
|
background: #282833
|
||||||
|
color: #cde
|
||||||
|
|
||||||
|
> div.row
|
||||||
|
width: 100%
|
||||||
|
padding: 0 0.4ch
|
||||||
|
box-sizing: border-box
|
||||||
|
width: 100%
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
flex-wrap: wrap
|
||||||
|
align-content: center
|
||||||
|
justify-content: space-between
|
||||||
|
|
||||||
|
> div.title
|
||||||
|
font-size: 0.75em
|
||||||
|
text-align: left
|
||||||
|
margin-top: 0.4em
|
||||||
|
margin-right: 1ch
|
||||||
|
height: 1.6em
|
||||||
|
user-select: none
|
||||||
|
margin-left: 0.4ch
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getSelectedContext } from "$lib/contexts/SelectContext.svelte";
|
||||||
|
|
||||||
|
export let name: string;
|
||||||
|
export let devices: {id: string}[];
|
||||||
|
|
||||||
|
function toggleAll() {
|
||||||
|
toggleMultiSelection(devices.map(d => d.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
const {toggleMultiSelection} = getSelectedContext();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="room-header">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div on:click={toggleAll} class="name">{name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
div.room-header
|
||||||
|
user-select: none
|
||||||
|
font-size: 2rem
|
||||||
|
color: #abc
|
||||||
|
border-bottom: 1px solid #123
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
export interface SelectContextData {
|
export interface SelectContextData {
|
||||||
toggleSelection(id: string): void
|
toggleSelection(id: string): void
|
||||||
|
toggleMultiSelection(ids: string[]): void
|
||||||
selectedList: Readable<string[]>
|
selectedList: Readable<string[]>
|
||||||
selectedMap: Readable<{[id:string]: boolean}>
|
selectedMap: Readable<{[id:string]: boolean}>
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,16 @@
|
|||||||
selectedMap.update(m => ({...m, [id]: !m[id]}));
|
selectedMap.update(m => ({...m, [id]: !m[id]}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleMultiSelection(ids: string[]) {
|
||||||
|
selectedMap.update(m => {
|
||||||
|
if (ids.find(i => !m[i])) {
|
||||||
|
return ids.reduce((m, i) => ({...m, [i]: true}), m)
|
||||||
|
} else {
|
||||||
|
return ids.reduce((m, i) => ({...m, [i]: false}), m)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Effect: remove non-existent devices
|
// Effect: remove non-existent devices
|
||||||
$: {
|
$: {
|
||||||
const newMap = {...$selectedMap};
|
const newMap = {...$selectedMap};
|
||||||
@@ -48,6 +59,7 @@
|
|||||||
selectedList: {subscribe: selectedList.subscribe},
|
selectedList: {subscribe: selectedList.subscribe},
|
||||||
selectedMap: {subscribe: selectedMap.subscribe},
|
selectedMap: {subscribe: selectedMap.subscribe},
|
||||||
toggleSelection,
|
toggleSelection,
|
||||||
|
toggleMultiSelection,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
state: Readable<UIState>
|
state: Readable<UIState>
|
||||||
error: Readable<string | null>
|
error: Readable<string | null>
|
||||||
deviceList: Readable<Device[]>
|
deviceList: Readable<Device[]>
|
||||||
roomList: Readable<{name: string, devices: Device[]}[]>
|
roomList: Readable<{name: string, groups: {name: string, devices: Device[]}[], devices: Device[]}[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStateContext(): StateContextData {
|
export function getStateContext(): StateContextData {
|
||||||
@@ -44,22 +44,44 @@
|
|||||||
|
|
||||||
const roomList = derived(state, state => {
|
const roomList = derived(state, state => {
|
||||||
const roomMap: Record<string, Device[]> = {};
|
const roomMap: Record<string, Device[]> = {};
|
||||||
|
const groupMap: Record<string, string> = {};
|
||||||
for (const id in state.devices) {
|
for (const id in state.devices) {
|
||||||
if (!state.devices.hasOwnProperty(id)) {
|
if (!state.devices.hasOwnProperty(id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomAlias = state.devices[id].aliases.find(a => a.startsWith("lucifer:room:"));
|
const roomAlias = state.devices[id].aliases.find(a => a.startsWith("lucifer:room:"));
|
||||||
|
const groupAlias = state.devices[id].aliases.find(a => a.startsWith("lucifer:group:"));
|
||||||
const roomName = roomAlias?.slice("lucifer:room:".length) || "Unroomed";
|
const roomName = roomAlias?.slice("lucifer:room:".length) || "Unroomed";
|
||||||
|
const groupName = groupAlias?.slice("lucifer:group:".length) || "";
|
||||||
|
|
||||||
if (!roomMap[roomName]) {
|
if (!roomMap[roomName]) {
|
||||||
roomMap[roomName] = [];
|
roomMap[roomName] = [];
|
||||||
}
|
}
|
||||||
|
if (groupAlias) {
|
||||||
|
groupMap[id] = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
roomMap[roomName].push(state.devices[id]);
|
roomMap[roomName].push(state.devices[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.keys(roomMap)
|
return Object.keys(roomMap)
|
||||||
.map(k => ({ name: k, devices: roomMap[k] }))
|
.map(k => ({
|
||||||
|
name: k,
|
||||||
|
devices: roomMap[k].filter(d => !groupMap[d.id]).sort((a,b) => a.name.localeCompare(b.name)),
|
||||||
|
groups: roomMap[k].filter(d => !!groupMap[d.id]).reduce<{name: string, devices: Device[]}[]>((p, d) => {
|
||||||
|
const g = p.find(e => e.name === groupMap[d.id]);
|
||||||
|
if (g == null) {
|
||||||
|
p.push({name:groupMap[d.id], devices: [d]})
|
||||||
|
} else {
|
||||||
|
g.devices.push(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}, [])
|
||||||
|
.map(g => ({...g, devices: g.devices.sort((a,b) => a.name.localeCompare(b.name))}))
|
||||||
|
.sort((a,b) => a.name.localeCompare(b.name))
|
||||||
|
}))
|
||||||
.sort((a,b) => a.name.localeCompare(b.name));
|
.sort((a,b) => a.name.localeCompare(b.name));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,103 +105,105 @@
|
|||||||
const currSocket = new WebSocket(url);
|
const currSocket = new WebSocket(url);
|
||||||
|
|
||||||
currSocket.onmessage = (msg) => {
|
currSocket.onmessage = (msg) => {
|
||||||
const patch: UIStatePatch = JSON.parse(msg.data);
|
const patches: UIStatePatch[] = JSON.parse(msg.data);
|
||||||
|
|
||||||
state.update(s => {
|
state.update(s => {
|
||||||
if (patch.device) {
|
for (const patch of patches) {
|
||||||
if (patch.device.delete) {
|
if (patch.device) {
|
||||||
const devices = {...s.devices};
|
if (patch.device.delete) {
|
||||||
delete devices[patch.device.id];
|
const devices = {...s.devices};
|
||||||
return {...s, devices};
|
delete devices[patch.device.id];
|
||||||
} else {
|
s = {...s, devices};
|
||||||
return {
|
} else {
|
||||||
...s,
|
s = {
|
||||||
devices: {
|
...s,
|
||||||
...s.devices,
|
devices: {
|
||||||
[patch.device.id]: ((patch) => {
|
...s.devices,
|
||||||
if (patch.addAlias) {
|
[patch.device.id]: ((patch) => {
|
||||||
const aliases = [...(s.devices[patch.id].aliases || [])];
|
if (patch.addAlias) {
|
||||||
const exclPrefix = ["lucifer:icon:", "lucifer:group:", "lucifer:name:"].find(p => patch.addAlias?.startsWith(p));
|
const aliases = [...(s.devices[patch.id].aliases || [])];
|
||||||
const exclExisting = exclPrefix && aliases.find(a => a.startsWith(exclPrefix));
|
const exclPrefix = ["lucifer:icon:", "lucifer:group:", "lucifer:name:"].find(p => patch.addAlias?.startsWith(p));
|
||||||
|
const exclExisting = exclPrefix && aliases.find(a => a.startsWith(exclPrefix));
|
||||||
|
|
||||||
if (patch.addAlias.startsWith("lucifer:name:")) {
|
if (patch.addAlias.startsWith("lucifer:name:")) {
|
||||||
patch.name = patch.addAlias.slice("lucifer:name:".length)
|
patch.name = patch.addAlias.slice("lucifer:name:".length)
|
||||||
}
|
}
|
||||||
if (patch.addAlias.startsWith("lucifer:icon:")) {
|
if (patch.addAlias.startsWith("lucifer:icon:")) {
|
||||||
patch.icon = patch.addAlias.slice("lucifer:icon:".length) as IconName
|
patch.icon = patch.addAlias.slice("lucifer:icon:".length) as IconName
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exclExisting) {
|
if (exclExisting) {
|
||||||
|
return {
|
||||||
|
...s.devices[patch.id],
|
||||||
|
aliases: [...aliases.filter(a => a != exclExisting), patch.addAlias].sort(),
|
||||||
|
name: patch.name || s.devices[patch.id].name,
|
||||||
|
icon: patch.icon || s.devices[patch.id].icon,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...s.devices[patch.id],
|
||||||
|
aliases: [...aliases, patch.addAlias].sort(),
|
||||||
|
name: patch.name || s.devices[patch.id].name,
|
||||||
|
icon: patch.icon || s.devices[patch.id].icon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (patch.removeAlias != null) {
|
||||||
|
const aliases = [...(s.devices[patch.id].aliases || [])];
|
||||||
return {
|
return {
|
||||||
...s.devices[patch.id],
|
...s.devices[patch.id],
|
||||||
aliases: [...aliases.filter(a => a != exclExisting), patch.addAlias].sort(),
|
aliases: aliases.filter(a => a !== patch.removeAlias),
|
||||||
name: patch.name || s.devices[patch.id].name,
|
|
||||||
icon: patch.icon || s.devices[patch.id].icon,
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...s.devices[patch.id],
|
...s.devices[patch.id],
|
||||||
aliases: [...aliases, patch.addAlias].sort(),
|
...patch,
|
||||||
name: patch.name || s.devices[patch.id].name,
|
|
||||||
icon: patch.icon || s.devices[patch.id].icon,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (patch.removeAlias != null) {
|
})(patch.device)
|
||||||
const aliases = [...(s.devices[patch.id].aliases || [])];
|
}
|
||||||
return {
|
}
|
||||||
...s.devices[patch.id],
|
}
|
||||||
aliases: aliases.filter(a => a !== patch.removeAlias),
|
}
|
||||||
};
|
|
||||||
} else {
|
if (patch.assignment) {
|
||||||
return {
|
if (patch.assignment.delete) {
|
||||||
...s.devices[patch.id],
|
const assignments = {...s.assignments};
|
||||||
...patch,
|
delete assignments[patch.assignment.id];
|
||||||
};
|
s = {...s, assignments};
|
||||||
|
} else {
|
||||||
|
s = {
|
||||||
|
...s,
|
||||||
|
assignments: {
|
||||||
|
...s.assignments,
|
||||||
|
[patch.assignment.id]: {
|
||||||
|
...s.assignments[patch.assignment.id],
|
||||||
|
...patch.assignment,
|
||||||
}
|
}
|
||||||
})(patch.device)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (patch.assignment) {
|
if (patch.script) {
|
||||||
if (patch.assignment.delete) {
|
if (patch.script.delete) {
|
||||||
const assignments = {...s.assignments};
|
const scripts = {...s.scripts};
|
||||||
delete assignments[patch.assignment.id];
|
delete scripts[patch.script.id];
|
||||||
return {...s, assignments};
|
s = {...s, scripts};
|
||||||
} else {
|
} else {
|
||||||
return {
|
s = {
|
||||||
...s,
|
...s,
|
||||||
assignments: {
|
scripts: {
|
||||||
...s.assignments,
|
...s.scripts,
|
||||||
[patch.assignment.id]: {
|
[patch.script.id]: {
|
||||||
...s.assignments[patch.assignment.id],
|
...s.scripts[patch.script.id],
|
||||||
...patch.assignment,
|
...patch.script,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (patch.script) {
|
|
||||||
if (patch.script.delete) {
|
|
||||||
const scripts = {...s.scripts};
|
|
||||||
delete scripts[patch.script.id];
|
|
||||||
return {...s, scripts};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
...s,
|
|
||||||
scripts: {
|
|
||||||
...s.scripts,
|
|
||||||
[patch.script.id]: {
|
|
||||||
...s.scripts[patch.script.id],
|
|
||||||
...patch.script,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,48 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DeviceIcon from "$lib/components/DeviceIcon.svelte";
|
import DeviceIcon from "$lib/components/DeviceIcon.svelte";
|
||||||
import Lamp, { DARK_COLOR } from "$lib/components/Lamp.svelte";
|
import Lamp, { DARK_COLOR } from "$lib/components/Lamp.svelte";
|
||||||
|
import MetaLamp from "$lib/components/MetaLamp.svelte";
|
||||||
|
import RoomHeader from "$lib/components/RoomHeader.svelte";
|
||||||
import { getStateContext } from "$lib/contexts/StateContext.svelte";
|
import { getStateContext } from "$lib/contexts/StateContext.svelte";
|
||||||
import { rgb } from "$lib/models/color";
|
import { rgb } from "$lib/models/color";
|
||||||
|
|
||||||
const {deviceList, roomList} = getStateContext();
|
const {roomList} = getStateContext();
|
||||||
|
|
||||||
|
$: console.log($roomList);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
{#each $deviceList as device (device.id) }
|
{#each $roomList as room (room.name)}
|
||||||
<Lamp compact={device.name.includes("Square")} device={device} />
|
<RoomHeader devices={room.devices} name={room.name} />
|
||||||
|
<div class="devices">
|
||||||
|
{#each room.devices as device (device.id) }
|
||||||
|
<Lamp device={device} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="devices">
|
||||||
|
{#each room.groups as group (group.name)}
|
||||||
|
<MetaLamp name={group.name} devices={group.devices} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
|
||||||
<div class="page" style="font-size: 8em">
|
|
||||||
<DeviceIcon name="hue_go" brightColor={rgb(0.517,0.537,1.000)} darkColor={DARK_COLOR} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div.page {
|
div.page {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100ch;
|
width: 200ch;
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: 2em;
|
font-size: 1.33rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.devices {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
div.devices + div.devices {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var zeroUUID = uuid.UUID{
|
var zeroUUID = uuid.UUID{
|
||||||
@@ -57,11 +58,34 @@ func New(addr string) (lucifer3.Service, error) {
|
|||||||
defer svc.removeSub(sub)
|
defer svc.removeSub(sub)
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
for patch := range sub {
|
lastSent := time.Now()
|
||||||
err := ws.WriteJSON(patch)
|
patches := make([]uistate.Patch, 0, 128)
|
||||||
|
for {
|
||||||
|
patch := <-sub
|
||||||
|
since := time.Now().Sub(lastSent)
|
||||||
|
patches = append(patches[:0], patch)
|
||||||
|
|
||||||
|
if since < time.Millisecond*950 {
|
||||||
|
waitCh := time.NewTimer(time.Millisecond*1000 - since)
|
||||||
|
waiting := true
|
||||||
|
|
||||||
|
for waiting {
|
||||||
|
select {
|
||||||
|
case patch, ok := <-sub:
|
||||||
|
patches = append(patches, patch)
|
||||||
|
waiting = ok
|
||||||
|
case <-waitCh.C:
|
||||||
|
waiting = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ws.WriteJSON(patches)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastSent = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user