summaryrefslogtreecommitdiffstatshomepage
path: root/frontend/src/lib/selection/Selection.svelte.ts
diff options
context:
space:
mode:
authorWolfgang Müller2025-02-13 17:52:16 +0100
committerWolfgang Müller2025-02-13 17:52:16 +0100
commitdc4db405d2991d3ec6a114f3b08d3fccd057d3ee (patch)
tree2c620c9af2062ba09fa591f8b3ed961664adab58 /frontend/src/lib/selection/Selection.svelte.ts
parent4df870d793123be95c8af031a340a39b5b8402ac (diff)
downloadhircine-dc4db405d2991d3ec6a114f3b08d3fccd057d3ee.tar.gz
frontend: Migrate to Svelte 5
Diffstat (limited to 'frontend/src/lib/selection/Selection.svelte.ts')
-rw-r--r--frontend/src/lib/selection/Selection.svelte.ts121
1 files changed, 121 insertions, 0 deletions
diff --git a/frontend/src/lib/selection/Selection.svelte.ts b/frontend/src/lib/selection/Selection.svelte.ts
new file mode 100644
index 0000000..dc294d0
--- /dev/null
+++ b/frontend/src/lib/selection/Selection.svelte.ts
@@ -0,0 +1,121 @@
+import { getContext, setContext } from 'svelte';
+import { SvelteSet } from 'svelte/reactivity';
+import { range } from '../Utils';
+
+interface Selectable {
+ id: number;
+}
+
+export function initSelectionContext<T extends Selectable>(
+ typename: string,
+ toName: (item: T) => string,
+ selectable?: (item: T) => boolean
+) {
+ return setContext('selection', new ItemSelection(typename, toName, selectable));
+}
+
+export function getSelectionContext<T extends Selectable>() {
+ return getContext<ItemSelection<T>>('selection');
+}
+
+export class ItemSelection<T extends Selectable> {
+ active = $state(false);
+ view: T[] = $state([]);
+
+ #ids = $state(new SvelteSet<number>());
+ #masked = $derived(new SvelteSet([...this.#ids].filter((i) => this.#indexOf(i) >= 0)));
+
+ typename: string;
+ #toName: (item: T) => string;
+ selectable: (item: T) => boolean;
+
+ constructor(
+ typename: string,
+ toName: (item: T) => string,
+ selectable: (item: T) => boolean = () => true
+ ) {
+ this.typename = typename;
+ this.#toName = toName;
+ this.selectable = selectable;
+ }
+
+ #indexOf = (id: number) => this.view.findIndex((v) => v.id === id);
+
+ update = (index: number, shift: boolean) => {
+ const id = this.view[index].id;
+
+ const selectableRange = (first: number, last: number) =>
+ range(first, last)
+ .filter((i) => this.selectable(this.view[i]))
+ .map((i) => this.view[i].id);
+
+ if (shift) {
+ const indices = this.indices;
+
+ const first = indices.at(0);
+ const last = indices.at(-1);
+
+ if (first === undefined || last === undefined) {
+ this.#ids.add(id);
+ } else if (index === first || index === last) {
+ this.#ids.clear();
+ } else if (index > last) {
+ this.#ids = new SvelteSet([...this.#ids, ...selectableRange(last, index)]);
+ } else if (index < last) {
+ this.#ids = new SvelteSet([...this.#ids, ...selectableRange(index, last)]);
+ }
+ } else {
+ if (this.#ids.has(id)) {
+ this.#ids.delete(id);
+ } else {
+ this.#ids.add(id);
+ }
+ }
+ };
+
+ toggle = () => {
+ this.active = !this.active;
+
+ if (!this.active) {
+ this.none();
+ }
+ };
+
+ all = () => {
+ this.#ids = new SvelteSet(this.view.filter(this.selectable).map((i) => i.id));
+ };
+
+ none = () => {
+ this.#ids.clear();
+ this.#masked.clear();
+ };
+
+ clear = () => {
+ this.active = false;
+ this.none();
+ };
+
+ contains(id: number) {
+ return this.#masked.has(id);
+ }
+
+ get ids() {
+ return [...this.#masked];
+ }
+
+ get size() {
+ return this.#masked.size;
+ }
+
+ get indices() {
+ return [...this.#ids].map(this.#indexOf).filter((i) => i >= 0);
+ }
+
+ get items() {
+ return this.indices.map((i) => this.view[i]);
+ }
+
+ get names() {
+ return this.items.map(this.#toName);
+ }
+}