upend/webui/src/lib/components/EntitySelect.svelte

162 lines
3.8 KiB
Svelte

<script lang="ts" context="module">
import { writable } from 'svelte/store';
export const selected = writable<string[]>([]);
</script>
<script lang="ts">
import { onMount } from 'svelte';
import { i18n } from '../i18n';
let canvas: HTMLCanvasElement;
onMount(() => {
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
let selecting = false;
let selectAllArea: DOMRect | undefined = undefined;
let selectAllAddresses: string[] = [];
let addressesToRemove = new Set();
document.addEventListener('mousedown', (ev) => {
if (!ctx) return;
if (ev.ctrlKey || ev.metaKey) {
ev.preventDefault();
selecting = true;
addressesToRemove = new Set();
const el = document.elementFromPoint(ev.clientX, ev.clientY) as HTMLElement;
const multiElement = el.closest('[data-address-multi]') as HTMLElement | undefined;
if (multiElement) {
const banner = multiElement.querySelector('h2');
if (banner) {
const rect = banner.getBoundingClientRect();
selectAllArea = rect;
selectAllAddresses = multiElement.dataset.addressMulti?.split(',') || [];
ctx.rect(rect.left, rect.top, rect.width, rect.height);
ctx.fillStyle = '#dc322f33';
ctx.fill();
ctx.fillStyle = '#dc322f77';
ctx.font = `bold ${rect.height / 2}px Inter`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const fix = ctx.measureText('M').actualBoundingBoxDescent / 2;
ctx.fillText(
$i18n.t('Select All'),
rect.left + rect.width / 2,
rect.top + rect.height / 2 + fix
);
}
}
ctx.strokeStyle = '#dc322f77';
ctx.lineWidth = 7;
ctx.beginPath();
ctx.moveTo(ev.clientX, ev.clientY);
}
});
document.addEventListener('mousemove', (ev) => {
if (!ctx) return;
if (selecting) {
ev.preventDefault();
if (selectAllArea) {
if (
ev.clientX > selectAllArea.left &&
ev.clientX < selectAllArea.right &&
ev.clientY > selectAllArea.top &&
ev.clientY < selectAllArea.bottom
) {
selected.update((selected) => {
return [
...selected,
...selectAllAddresses.filter((a) => {
return !selected.includes(a);
})
];
});
stop();
}
}
const el = document.elementFromPoint(ev.clientX, ev.clientY) as HTMLElement;
const addressElement = el.closest('[data-address]') as HTMLElement | undefined;
if (addressElement) {
const address = addressElement.dataset.address;
const selectMode = addressElement.dataset.selectMode;
if (selectMode === 'add' || selectMode === undefined) {
selected.update((selected) => {
if (address && !selected.includes(address)) {
return [...selected, address];
} else {
return selected;
}
});
} else if (selectMode === 'remove') {
addressesToRemove.add(address);
}
}
ctx.lineTo(ev.clientX, ev.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(ev.clientX, ev.clientY);
}
});
document.addEventListener('mouseup', () => {
stop();
});
function stop() {
selectAllArea = undefined;
selectAllAddresses = [];
selecting = false;
ctx?.clearRect(0, 0, canvas.width, canvas.height);
for (const address of addressesToRemove) {
selected.update((selected) => {
return selected.filter((a) => a !== address);
});
}
}
});
</script>
<div class="selectIndicator">
<canvas bind:this={canvas}></canvas>
</div>
<style lang="scss">
.selectIndicator {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
pointer-events: none;
canvas {
width: 100%;
height: 100%;
}
}
</style>