upend/webui/src/components/EntitySelect.svelte

168 lines
4.4 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[] | undefined = undefined;
let addressesToRemove = new Set();
document.addEventListener("mousedown", (ev) => {
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 (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 (!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 = undefined;
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>