Rename RedrawableHTML, remove File API rate limite and fix pull job transaction.

This commit is contained in:
Clément Pons
2026-01-27 17:13:40 +01:00
parent e9a892076d
commit a412116b9c
14 changed files with 153 additions and 117 deletions

View File

@@ -1,5 +1,5 @@
import type { RouteLocationAsRelativeTyped, RouteLocationRaw, RouteMapGeneric } from "vue-router";
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node, type RedrawableHTML } from "#shared/dom";
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node, type HTMLElement } from "#shared/dom";
import { contextmenu, followermenu, minimizeBox, popper, teleport, tooltip, type FloatState } from "#shared/floating";
import { clamp } from "#shared/general";
import { Tree } from "#shared/tree";
@@ -18,11 +18,11 @@ export function link(children: NodeChildren, properties?: NodeProperties & { act
}
} : undefined }, children);
}
export function loading(size: 'small' | 'normal' | 'large' = 'normal'): RedrawableHTML
export function loading(size: 'small' | 'normal' | 'large' = 'normal'): HTMLElement
{
return dom('span', { class: ["after:block after:relative after:rounded-full after:border-transparent after:border-t-accent-purple after:animate-spin", {'w-6 h-6 border-4 border-transparent after:-top-[4px] after:-left-[4px] after:w-6 after:h-6 after:border-4': size === 'normal', 'w-4 h-4 after:-top-[2px] after:-left-[2px] after:w-4 after:h-4 after:border-2': size === 'small', 'w-12 h-12 after:-top-[6px] after:-left-[6px] after:w-12 after:h-12 after:border-[6px]': size === 'large'}] })
}
export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise<RedrawableHTML>)
export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise<HTMLElement>)
{
let state = { current: loading(size) };
@@ -36,7 +36,7 @@ export function async(size: 'small' | 'normal' | 'large' = 'normal', fn: Promise
return state;
}
export function button(content: Node | NodeChildren, onClick?: (this: RedrawableHTML) => void, cls?: Class)
export function button(content: Node | NodeChildren, onClick?: (this: HTMLElement) => void, cls?: Class)
{
const btn = dom('button', { class: [`inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
@@ -73,17 +73,17 @@ export function buttongroup<T extends any>(options: Array<{ text: string, value:
}}}))
return div(['flex flex-row', settings?.class?.container], elements);
}
export function optionmenu(options: Array<{ title: string, click: () => void }>, settings?: { position?: Placement, class?: { container?: Class, option?: Class } }): (target?: RedrawableHTML) => void
export function optionmenu(options: Array<{ title: string, click: () => void }>, settings?: { position?: Placement, class?: { container?: Class, option?: Class } }): (target?: HTMLElement) => void
{
let close: () => void;
const element = div(['flex flex-col divide-y divide-light-30 dark:divide-dark-30 text-light-100 dark:text-dark-100', settings?.class?.container], options.map(e => dom('div', { class: ['flex flex-row px-2 py-1 hover:bg-light-35 dark:hover:bg-dark-35 cursor-pointer', settings?.class?.option], text: e.title, listeners: { click: () => { e.click(); close() } } })));
return function(this: RedrawableHTML, target?: RedrawableHTML) {
return function(this: HTMLElement, target?: HTMLElement) {
close = followermenu(target ?? this, [ element ], { arrow: true, placement: settings?.position, offset: 8 }).close;
}
}
export type Option<T> = { text: string, render?: () => RedrawableHTML, value: T | Option<T>[] } | undefined;
type StoredOption<T> = { item: Option<T>, dom: RedrawableHTML, container?: RedrawableHTML, children?: Array<StoredOption<T>> };
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): RedrawableHTML
export type Option<T> = { text: string, render?: () => HTMLElement, value: T | Option<T>[] } | undefined;
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
{
let context: { close: Function };
let focused: number | undefined;
@@ -151,7 +151,7 @@ export function select<T extends NonNullable<any>>(options: Array<{ text: string
})
return select;
}
export function multiselect<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): RedrawableHTML
export function multiselect<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
{
let context: { close: Function };
let focused: number | undefined;
@@ -225,7 +225,7 @@ export function multiselect<T extends NonNullable<any>>(options: Array<{ text: s
}
export function combobox<T extends NonNullable<any>>(options: Option<T>[], settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
{
let context: { container: RedrawableHTML, content: NodeChildren, close: () => void };
let context: { container: HTMLElement, content: NodeChildren, close: () => void };
let selected = true, tree: StoredOption<T>[] = [];
let focused: number | undefined;
let currentOptions: StoredOption<T>[] = [];
@@ -470,10 +470,10 @@ export function foldable(content: Reactive<NodeChildren>, title: NodeChildren, s
fold.toggleAttribute('data-active', settings?.open ?? true);
return fold;
}
type TableRow = Record<string, (() => RedrawableHTML) | RedrawableHTML | string>;
type TableRow = Record<string, (() => HTMLElement) | HTMLElement | string>;
export function table(content: TableRow[], headers: TableRow, properties?: { class?: { table?: Class, header?: Class, body?: Class, row?: Class, cell?: Class } })
{
const render = (item: (() => RedrawableHTML) | RedrawableHTML | string) => typeof item === 'string' ? text(item) : typeof item === 'function' ? item() : item;
const render = (item: (() => HTMLElement) | HTMLElement | string) => typeof item === 'string' ? text(item) : typeof item === 'function' ? item() : item;
return dom('table', { class: ['', properties?.class?.table] }, [ dom('thead', { class: ['', properties?.class?.header] }, [ dom('tr', { class: '' }, Object.values(headers).map(e => dom('th', {}, [ render(e) ]))) ]), dom('tbody', { class: ['', properties?.class?.body] }, content.map(e => dom('tr', { class: ['', properties?.class?.row] }, Object.keys(headers).map(f => e.hasOwnProperty(f) ? dom('td', { class: ['', properties?.class?.cell] }, [ render(e[f]!) ]) : undefined)))) ]);
}
export function toggle(settings?: { defaultValue?: boolean, change?: (value: boolean) => void, disabled?: boolean, class?: { container?: Class } })
@@ -494,7 +494,7 @@ export function toggle(settings?: { defaultValue?: boolean, change?: (value: boo
}, [ div('block w-[18px] h-[18px] translate-x-[2px] will-change-transform transition-transform bg-light-50 dark:bg-dark-50 group-data-[state=checked]:translate-x-[26px] group-data-[disabled]:bg-light-30 dark:group-data-[disabled]:bg-dark-30 group-data-[disabled]:border-light-30 dark:group-data-[disabled]:border-dark-30') ]);
return element;
}
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: RedrawableHTML, value: boolean) => void, disabled?: boolean, class?: { container?: Class, icon?: Class } })
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HTMLElement, value: boolean) => void, disabled?: boolean, class?: { container?: Class, icon?: Class } })
{
let state = settings?.defaultValue ?? false;
const element = dom("div", { class: [`group w-6 h-6 box-content flex items-center justify-center border border-light-50 dark:border-dark-50 bg-light-20 dark:bg-dark-20
@@ -512,7 +512,7 @@ export function checkbox(settings?: { defaultValue?: boolean, change?: (this: Re
}, [ icon('radix-icons:check', { width: 14, height: 14, class: ['hidden group-data-[state="checked"]:block data-[disabled]:text-light-50 dark:data-[disabled]:text-dark-50', settings?.class?.icon] }), ]);
return element;
}
export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content: Reactive<NodeChildren> }>, settings?: { focused?: string, class?: { container?: Class, tabbar?: Class, title?: Class, content?: Class }, switch?: (tab: string) => void | boolean }): RedrawableHTML
export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content: Reactive<NodeChildren> }>, settings?: { focused?: string, class?: { container?: Class, tabbar?: Class, title?: Class, content?: Class }, switch?: (tab: string) => void | boolean }): HTMLElement
{
let focus = settings?.focused ?? tabs[0]?.id;
const titles = tabs.map(e => dom('div', { class: ['px-2 py-1 border-b border-transparent hover:border-accent-blue data-[focus]:border-accent-blue data-[focus]:border-b-[3px] cursor-pointer', settings?.class?.title], attributes: { 'data-focus': e.id === focus }, listeners: { click: function() {
@@ -535,15 +535,15 @@ export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content:
div(['flex flex-row items-center gap-1', settings?.class?.tabbar], titles),
content
]);
return container as RedrawableHTML;
return container as HTMLElement;
}
export function floater(container: RedrawableHTML, content: NodeChildren | (() => NodeChildren), settings?: { delay?: number, href?: RouteLocationRaw, class?: Class, style?: Record<string, string | undefined | boolean | number> | string, position?: Placement, pinned?: boolean | { width: number, height: number }, minimizable?: boolean, cover?: 'width' | 'height' | 'all' | 'none', events?: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (state: FloatState) => boolean, onhide?: (state: FloatState) => boolean }, title?: string })
export function floater(container: HTMLElement, content: NodeChildren | (() => NodeChildren), settings?: { delay?: number, href?: RouteLocationRaw, class?: Class, style?: Record<string, string | undefined | boolean | number> | string, position?: Placement, pinned?: boolean | { width: number, height: number }, minimizable?: boolean, cover?: 'width' | 'height' | 'all' | 'none', events?: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (state: FloatState) => boolean, onhide?: (state: FloatState) => boolean }, title?: string })
{
let viewport = document.getElementById('mainContainer') ?? undefined;
let diffX, diffY;
let minimizeRect: DOMRect, minimized = false;
const events: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (this: RedrawableHTML, state: FloatState) => boolean, onhide?: (this: RedrawableHTML, state: FloatState) => boolean } = Object.assign({
const events: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (this: HTMLElement, state: FloatState) => boolean, onhide?: (this: HTMLElement, state: FloatState) => boolean } = Object.assign({
show: ['mouseenter', 'mousemove', 'focus'],
hide: ['mouseleave', 'blur'],
} as { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap> }, settings?.events ?? {});
@@ -634,7 +634,7 @@ export function floater(container: RedrawableHTML, content: NodeChildren | (() =
if(!floating.content.hasAttribute('data-pinned'))
return;
[...this.parentElement?.children ?? []].forEach(e => (e as any as RedrawableHTML).attributeStyleMap.set('z-index', -1));
[...this.parentElement?.children ?? []].forEach(e => (e as any as HTMLElement).attributeStyleMap.set('z-index', -1));
this.attributeStyleMap.set('z-index', 0);
}, { passive: true });
}
@@ -717,13 +717,13 @@ export interface ToastConfig
timer?: boolean
type?: ToastType
}
type ToastDom = ToastConfig & { dom: RedrawableHTML };
type ToastDom = ToastConfig & { dom: HTMLElement };
export type ToastType = 'info' | 'success' | 'error';
export class Toaster
{
private static _MAX_DRAG = 150;
private static _list: Array<ToastDom> = [];
private static _container: RedrawableHTML;
private static _container: HTMLElement;
static init()
{
@@ -793,4 +793,23 @@ export class Toaster
], { easing: 'ease-in', duration: 100 }).onfinish = () => config.dom.remove();
Toaster._list = Toaster._list.filter(e => e !== config);
}
}
export class DiceRoller
{
private static _dom: HTMLElement;
static init()
{
DiceRoller.dispose();
DiceRoller._dom = dom('div', { attributes: { id: 'dice-roller' }, class: 'fixed bottom-0 right-0 flex flex-col p-6 gap-2 max-w-[512px] z-50 outline-none min-w-72 empty:hidden' });
document.body.appendChild(DiceRoller._dom);
}
static dispose()
{
DiceRoller._dom?.remove();
}
static get dom()
{
return DiceRoller._dom;
}
}