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
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>
|