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.
 
 
 
 
 
 

136 lines
3.9 KiB

<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
toggleMultiSelection(ids: string[]): void
selectedMasks: Readable<string[]>
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 selectedMasks = writable<string[]>([]);
const {state, deviceList} = getStateContext();
function toggleSelection(id: string) {
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
$: {
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]);
$: {
const firstDevice = $deviceList.find(d => $selectedMap[d.id]);
const nextMasks: string[] = [];
if (firstDevice != null) {
// Common aliases first
for (const alias of firstDevice.aliases) {
if (alias.startsWith("lucifer:name") || alias.startsWith("lucifer:icon:")) {
continue;
}
let qualified = true;
for (const device of $deviceList) {
const isSelected = $selectedMap[device.id] || false;
const hasAlias = device.aliases.includes(alias);
if (isSelected !== hasAlias) {
qualified = false;
break
}
}
if (qualified) {
nextMasks.push(alias);
}
}
let shortestIdPrefix = firstDevice.id;
let shortestNamePrefix = firstDevice.name;
let nonames = false;
for (const device of $deviceList) {
if (!$selectedMap[device.id]) {
continue
}
if (device.name === "") {
nonames = true;
}
let longestIdMatch = 0;
for (let i = 1; i <= shortestIdPrefix.length; ++i) {
if (device.id.startsWith(shortestIdPrefix.slice(0, i))) {
longestIdMatch = i;
}
}
let longestNameMatch = 0;
for (let i = 1; i <= shortestNamePrefix.length; ++i) {
if (device.name.startsWith(shortestNamePrefix.slice(0, i))) {
longestNameMatch = i;
}
}
shortestIdPrefix = shortestIdPrefix.substring(0, longestIdMatch);
shortestNamePrefix = shortestNamePrefix.substring(0, longestNameMatch);
}
nextMasks.push(`${shortestIdPrefix}{${$selectedList.map(s => s.substring(shortestIdPrefix.length)).sort().join(",")}}`)
if (!nonames) {
nextMasks.push(`lucifer:name:${shortestNamePrefix}{${$selectedList.map(id => $state.devices[id].name).map(s => s.substring(shortestNamePrefix.length)).sort().join(",")}}`)
}
}
$selectedMasks = nextMasks.map(m => m.endsWith("{}") ? m.slice(0, -2) : m);
}
setContext(ctxKey, {
selectedList: {subscribe: selectedList.subscribe},
selectedMap: {subscribe: selectedMap.subscribe},
selectedMasks: {subscribe: selectedMasks.subscribe},
toggleSelection,
toggleMultiSelection,
});
</script>
<slot></slot>