@dvcol/neo-svelte
Version:
Neomorphic ui library for svelte 5
158 lines (157 loc) • 4.83 kB
JavaScript
import { getUUID } from '@dvcol/common-utils/common/string';
import { getContext, setContext } from 'svelte';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import { NeoProgressStatus } from './neo-progress.model.js';
import { NeoErrorProgressContextNotFound } from '../utils/error.utils.js';
/**
* Queuing service to keep track of concurrent call to progress bar
*/
export class NeoProgressService {
/** State of the progress bar */
#context;
/** Set of active progress IDs */
#active = new SvelteSet();
/** Queue of progress IDs with their timeout */
#queue = new SvelteMap();
/** Options for the progress service */
#options;
get context() {
return this.#context;
}
get value() {
return this.context.value;
}
get buffer() {
return this.context.buffer;
}
get status() {
return this.context.status;
}
get active() {
return this.#active;
}
constructor(context, options = {}) {
this.#context = context;
this.#options = options;
}
/**
* Synchronizes the progress state working with the current active IDs set.
*/
sync() {
if (this.status === NeoProgressStatus.Active)
return;
if (this.status === NeoProgressStatus.Indeterminate)
return;
this.#active.clear();
return this.active;
}
/**
* Starts the progress bar with the given ID and options.
* If the progress bar is already active, it resets it with the provided options instead.
*/
#start(id, opts) {
if (this.status === NeoProgressStatus.Active)
return this.context.reset(true, opts);
return this.context.start(opts);
}
/**
* Dequeues the progress bar with the given ID if it exists in the queue.
* @param id
* @private
*/
#deque(id) {
const timeout = this.#queue.get(id);
if (timeout === undefined)
return;
clearTimeout(timeout);
this.#queue.delete(id);
}
/**
* Deletes the progress bar with the given ID from the active set and dequeues it if it exists.
*/
#delete(id) {
this.#active.delete(id);
this.#deque(id);
}
/**
* Generates a unique ID for the progress bar.
*/
#generateId() {
return this.#options.uuid?.() ?? getUUID();
}
/**
* Starts a new progress bar with the given options.
* If an ID is provided, it will be used to track the progress bar.
* If no ID is provided, a new UUID will be generated.
* If a delay is provided, the progress bar will start after the specified delay.
*/
start(opts, { id = this.#generateId(), delay = this.#options.delay } = {}) {
this.sync();
this.#delete(id); // debounce queue if delay, skip queue if not
if (!delay) {
this.#active.add(id);
this.#start(id, opts);
}
else {
this.#queue.set(id, setTimeout(() => {
this.#active.add(id);
this.#start(id, opts);
this.#queue.delete(id);
}, delay));
}
return id;
}
cancel(id, force = !id) {
this.sync();
if (id)
this.#delete(id);
if (force || this.#active.size === 0)
void this.context.cancel();
return id;
}
complete(id, force = !id) {
this.sync();
if (id)
this.#delete(id);
if (force || this.#active.size === 0)
void this.context.complete();
return id;
}
error(id, force = !id) {
this.sync();
if (id)
this.#delete(id);
if (force || this.#active.size === 0)
void this.context.complete({ state: NeoProgressStatus.Error });
return id;
}
success(id, force = !id) {
this.sync();
if (id)
this.#delete(id);
if (force || this.#active.size === 0)
void this.context.complete({ state: NeoProgressStatus.Success });
return id;
}
warning(id, force = !id) {
this.sync();
if (id)
this.#delete(id);
if (force || this.#active.size === 0)
void this.context.complete({ state: NeoProgressStatus.Warning });
return id;
}
}
const NeoProgressContextSymbol = Symbol('NeoProgressContext');
export function getProgressContext() {
return getContext(NeoProgressContextSymbol);
}
export function setProgressContext(context) {
return setContext(NeoProgressContextSymbol, context);
}
export function useNeoProgressService() {
const context = getProgressContext();
if (!context)
throw new NeoErrorProgressContextNotFound();
return new NeoProgressService(context);
}