@vorplex/web 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/functions/class-names.function.d.ts +1 -0
- package/dist/functions/class-names.function.js +14 -0
- package/dist/functions/drag.function.d.ts +26 -0
- package/dist/functions/drag.function.js +76 -0
- package/dist/functions/observe-element.function.d.ts +3 -0
- package/dist/functions/observe-element.function.js +18 -0
- package/dist/functions/on-mouse-drag.function.d.ts +16 -0
- package/dist/functions/on-mouse-drag.function.js +61 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +11 -0
- package/dist/utils/css.util.d.ts +12 -0
- package/dist/utils/css.util.js +92 -0
- package/dist/utils/element.util.d.ts +20 -0
- package/dist/utils/element.util.js +57 -0
- package/dist/utils/file-manager.util.d.ts +12 -0
- package/dist/utils/file-manager.util.js +50 -0
- package/dist/utils/file-system.util.d.ts +20 -0
- package/dist/utils/file-system.util.js +63 -0
- package/dist/utils/indexed-db.util.d.ts +12 -0
- package/dist/utils/indexed-db.util.js +94 -0
- package/dist/utils/window-share.util.d.ts +4 -0
- package/dist/utils/window-share.util.js +9 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function classNames(...classes: (string | Record<string, boolean>)[]): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function classNames(...classes) {
|
|
2
|
+
let className = '';
|
|
3
|
+
for (const item of classes) {
|
|
4
|
+
if (typeof item === 'string') {
|
|
5
|
+
className = `${className} ${item}`.trim();
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
for (const key in item) {
|
|
9
|
+
className = `${className} ${item[key] ? key : ''}`.trim();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return className;
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface IDragOptions<T> {
|
|
2
|
+
event: DragEvent;
|
|
3
|
+
type: string;
|
|
4
|
+
data: T;
|
|
5
|
+
onDragStop?: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare class Drag {
|
|
8
|
+
private static listener;
|
|
9
|
+
static options: IDragOptions<any>;
|
|
10
|
+
private static targetElement;
|
|
11
|
+
static start<T>(options: IDragOptions<T>): void;
|
|
12
|
+
static get<T = any>(): {
|
|
13
|
+
options: IDragOptions<T>;
|
|
14
|
+
draggedElement: HTMLElement;
|
|
15
|
+
acceptedElement: HTMLElement;
|
|
16
|
+
};
|
|
17
|
+
static accepts<T>(options: {
|
|
18
|
+
event: DragEvent;
|
|
19
|
+
type: string;
|
|
20
|
+
condition?: (data: T, event: DragEvent) => boolean;
|
|
21
|
+
accepting?: (event: DragEvent, data: T) => () => void;
|
|
22
|
+
dropped: (data: T) => void;
|
|
23
|
+
}): boolean;
|
|
24
|
+
private static cleanupTarget;
|
|
25
|
+
private static cleanup;
|
|
26
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export class Drag {
|
|
2
|
+
static listener;
|
|
3
|
+
static options;
|
|
4
|
+
static targetElement;
|
|
5
|
+
static start(options) {
|
|
6
|
+
Drag.options = options;
|
|
7
|
+
Drag.targetElement = options.event.currentTarget;
|
|
8
|
+
options.event.currentTarget.addEventListener('dragend', () => {
|
|
9
|
+
options.onDragStop?.();
|
|
10
|
+
this.cleanup();
|
|
11
|
+
}, { once: true });
|
|
12
|
+
}
|
|
13
|
+
static get() {
|
|
14
|
+
return {
|
|
15
|
+
options: this.options,
|
|
16
|
+
draggedElement: this.targetElement,
|
|
17
|
+
acceptedElement: this.listener?.target
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
static accepts(options) {
|
|
21
|
+
if (!this.options)
|
|
22
|
+
return false;
|
|
23
|
+
if (options.event.currentTarget === this.targetElement)
|
|
24
|
+
return false;
|
|
25
|
+
if (Drag.options?.type !== options.type)
|
|
26
|
+
return false;
|
|
27
|
+
if (options.condition && !options.condition(Drag.options.data, options.event))
|
|
28
|
+
return;
|
|
29
|
+
const target = options.event.currentTarget;
|
|
30
|
+
if (this.listener?.target === target)
|
|
31
|
+
return false;
|
|
32
|
+
this.cleanupTarget();
|
|
33
|
+
let acceptingDispose;
|
|
34
|
+
this.listener = {
|
|
35
|
+
target,
|
|
36
|
+
drop: (e) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
options.dropped(this.options.data);
|
|
39
|
+
this.cleanup();
|
|
40
|
+
},
|
|
41
|
+
over: (event) => {
|
|
42
|
+
if (!this.options)
|
|
43
|
+
return;
|
|
44
|
+
if (event.currentTarget === this.targetElement)
|
|
45
|
+
return;
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
acceptingDispose = options.accepting?.(event, Drag.options.data);
|
|
48
|
+
},
|
|
49
|
+
leave: (e) => {
|
|
50
|
+
const relatedTarget = e.relatedTarget;
|
|
51
|
+
if (e.currentTarget === target && (target instanceof Node && (!relatedTarget || !target.contains(relatedTarget)))) {
|
|
52
|
+
this.cleanupTarget();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
dispose: () => {
|
|
56
|
+
acceptingDispose?.();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
target.addEventListener('drop', this.listener.drop, { once: true });
|
|
60
|
+
target.addEventListener('dragover', this.listener.over);
|
|
61
|
+
target.addEventListener('dragleave', this.listener.leave);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
static cleanupTarget() {
|
|
65
|
+
this.listener?.target.removeEventListener('dragover', this.listener.over);
|
|
66
|
+
this.listener?.target.removeEventListener('drop', this.listener.drop);
|
|
67
|
+
this.listener?.target.removeEventListener('dragleave', this.listener.leave);
|
|
68
|
+
this.listener?.dispose?.();
|
|
69
|
+
this.listener = null;
|
|
70
|
+
}
|
|
71
|
+
static cleanup() {
|
|
72
|
+
this.cleanupTarget();
|
|
73
|
+
this.options = null;
|
|
74
|
+
this.targetElement = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { $Value } from '@vorplex/core';
|
|
2
|
+
export function observeElement(element, observer) {
|
|
3
|
+
let disposed = false;
|
|
4
|
+
let previousRect = element.getBoundingClientRect();
|
|
5
|
+
function observe() {
|
|
6
|
+
requestAnimationFrame(() => {
|
|
7
|
+
const rect = element.getBoundingClientRect();
|
|
8
|
+
if (!$Value.equals(rect, previousRect)) {
|
|
9
|
+
observer.onTransform(rect, previousRect);
|
|
10
|
+
}
|
|
11
|
+
previousRect = rect;
|
|
12
|
+
if (!disposed)
|
|
13
|
+
observe();
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
observe();
|
|
17
|
+
return () => { disposed = true; };
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Point } from '@vorplex/core';
|
|
2
|
+
export interface MouseDragEvent {
|
|
3
|
+
mouseEvent: MouseEvent;
|
|
4
|
+
currentElement: HTMLElement;
|
|
5
|
+
delta: Point;
|
|
6
|
+
length: Point;
|
|
7
|
+
}
|
|
8
|
+
export interface MouseDragOptions {
|
|
9
|
+
event: MouseEvent;
|
|
10
|
+
snap?: number;
|
|
11
|
+
dragging?: (event: MouseDragEvent) => void;
|
|
12
|
+
dragged?: (delta: Point) => void;
|
|
13
|
+
resizing?: (event: MouseDragEvent) => void;
|
|
14
|
+
resized?: (delta: Point) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function onMouseDrag(options: MouseDragOptions): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { $Number, $Point } from '@vorplex/core';
|
|
2
|
+
export function onMouseDrag(options) {
|
|
3
|
+
options.event.stopPropagation();
|
|
4
|
+
const currentElement = options.event.currentTarget;
|
|
5
|
+
let action = 'drag';
|
|
6
|
+
if (options.resizing || options.resized) {
|
|
7
|
+
const rect = currentElement.getBoundingClientRect();
|
|
8
|
+
if (options.event.clientX === $Number.clamp(options.event.clientX, rect.left + rect.width - 15, rect.left + rect.width)) {
|
|
9
|
+
if (options.event.clientY === $Number.clamp(options.event.clientY, rect.top + rect.height - 15, rect.top + rect.height)) {
|
|
10
|
+
action = 'resize';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const snap = options.snap || 1;
|
|
15
|
+
const origin = $Point.create($Number.snap(options.event.clientX, snap), $Number.snap(options.event.clientY, snap));
|
|
16
|
+
let delta = origin;
|
|
17
|
+
const mouseMoveListener = (event) => {
|
|
18
|
+
const dragEvent = {
|
|
19
|
+
mouseEvent: options.event,
|
|
20
|
+
currentElement,
|
|
21
|
+
delta: {
|
|
22
|
+
x: $Number.snap(event.clientX - delta.x, snap),
|
|
23
|
+
y: $Number.snap(event.clientY - delta.y, snap),
|
|
24
|
+
},
|
|
25
|
+
length: {
|
|
26
|
+
x: $Number.snap(event.clientX - origin.x, snap),
|
|
27
|
+
y: $Number.snap(event.clientY - origin.y, snap),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
if (dragEvent.length.x !== 0 || dragEvent.length.y !== 0) {
|
|
31
|
+
if (action === 'drag') {
|
|
32
|
+
options.dragging && options.dragging(dragEvent);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
options.resizing && options.resizing(dragEvent);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
delta = $Point.create($Number.snap(event.clientX, snap), $Number.snap(event.clientY, snap));
|
|
39
|
+
};
|
|
40
|
+
const mouseUpListener = ({ clientX, clientY }) => {
|
|
41
|
+
document.removeEventListener('mousemove', mouseMoveListener, {
|
|
42
|
+
capture: true,
|
|
43
|
+
});
|
|
44
|
+
const delta = $Point.snap($Point.create(clientX - options.event.clientX, clientY - options.event.clientY), snap);
|
|
45
|
+
if (delta.x !== 0 || delta.y !== 0) {
|
|
46
|
+
if (action === 'drag') {
|
|
47
|
+
options.dragged && options.dragged(delta);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
options.resized && options.resized(delta);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
document.addEventListener('mousemove', mouseMoveListener, {
|
|
55
|
+
capture: true,
|
|
56
|
+
});
|
|
57
|
+
document.addEventListener('mouseup', mouseUpListener, {
|
|
58
|
+
capture: true,
|
|
59
|
+
once: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './functions/class-names.function';
|
|
2
|
+
export * from './functions/drag.function';
|
|
3
|
+
export * from './functions/observe-element.function';
|
|
4
|
+
export * from './functions/on-mouse-drag.function';
|
|
5
|
+
export * from './utils/css.util';
|
|
6
|
+
export * from './utils/element.util';
|
|
7
|
+
export * from './utils/file-manager.util';
|
|
8
|
+
export * from './utils/file-system.util';
|
|
9
|
+
export * from './utils/indexed-db.util';
|
|
10
|
+
export * from './utils/window-share.util';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// **/*.ts
|
|
2
|
+
export * from './functions/class-names.function';
|
|
3
|
+
export * from './functions/drag.function';
|
|
4
|
+
export * from './functions/observe-element.function';
|
|
5
|
+
export * from './functions/on-mouse-drag.function';
|
|
6
|
+
export * from './utils/css.util';
|
|
7
|
+
export * from './utils/element.util';
|
|
8
|
+
export * from './utils/file-manager.util';
|
|
9
|
+
export * from './utils/file-system.util';
|
|
10
|
+
export * from './utils/indexed-db.util';
|
|
11
|
+
export * from './utils/window-share.util';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class $CSS {
|
|
2
|
+
private static state;
|
|
3
|
+
private static worker;
|
|
4
|
+
private static messages;
|
|
5
|
+
static init(): Promise<void>;
|
|
6
|
+
static compileScss(scss: string): Promise<string>;
|
|
7
|
+
static getRules(css: string): CSSStyleRule[];
|
|
8
|
+
static getStyleProperties(style: CSSStyleDeclaration): Record<string, string>;
|
|
9
|
+
static getProperties(css: string): Record<string, Record<string, string>> | null;
|
|
10
|
+
static fromProperties(style: Record<string, Record<string, string>>): string;
|
|
11
|
+
static toStyleAttribute(style: Record<string, string>): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { $Id, $String, Emitter, State } from '@vorplex/core';
|
|
2
|
+
export class $CSS {
|
|
3
|
+
static state = new State(null);
|
|
4
|
+
static worker;
|
|
5
|
+
static messages = new Emitter();
|
|
6
|
+
static async init() {
|
|
7
|
+
if ($CSS.state.value === null) {
|
|
8
|
+
$CSS.state.update('busy');
|
|
9
|
+
const response = await fetch('https://unpkg.com/sass.js/dist/sass.worker.js');
|
|
10
|
+
const content = await response.text();
|
|
11
|
+
const url = URL.createObjectURL(new Blob([content], { type: 'text/javascript' }));
|
|
12
|
+
$CSS.worker = new Worker(url);
|
|
13
|
+
$CSS.worker.addEventListener('message', (event) => $CSS.messages.emit(event), false);
|
|
14
|
+
$CSS.state.update('loaded');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
static async compileScss(scss) {
|
|
18
|
+
$CSS.init();
|
|
19
|
+
await $CSS.state.until((state) => state.value === 'loaded');
|
|
20
|
+
const id = $Id.uuid();
|
|
21
|
+
$CSS.worker.postMessage({
|
|
22
|
+
id,
|
|
23
|
+
command: 'compile',
|
|
24
|
+
args: [scss],
|
|
25
|
+
});
|
|
26
|
+
const message = await $CSS.messages.until((message) => message.data.id === id);
|
|
27
|
+
return message.data.result.text;
|
|
28
|
+
}
|
|
29
|
+
static getRules(css) {
|
|
30
|
+
const sheet = new CSSStyleSheet();
|
|
31
|
+
const rules = css
|
|
32
|
+
.split('}')
|
|
33
|
+
.map((rule) => rule.trim())
|
|
34
|
+
.filter((rule) => rule)
|
|
35
|
+
.map((rule) => rule + '}');
|
|
36
|
+
for (const rule of rules) {
|
|
37
|
+
try {
|
|
38
|
+
sheet.insertRule(rule);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return Array.from(sheet.rules).filter((rule) => rule?.selectorText);
|
|
45
|
+
}
|
|
46
|
+
static getStyleProperties(style) {
|
|
47
|
+
const properties = {};
|
|
48
|
+
for (let i = 0; i < style.length; i++) {
|
|
49
|
+
const property = style.item(i);
|
|
50
|
+
let value = style.getPropertyValue(property).trim();
|
|
51
|
+
if (value.startsWith('url(')) {
|
|
52
|
+
value = value.replace('~', window.location.origin + window.location.pathname);
|
|
53
|
+
if (value.indexOf('^') < 6) {
|
|
54
|
+
value = value.replace('^', '');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
properties[$String.camelCase(property)] = value;
|
|
58
|
+
}
|
|
59
|
+
return properties;
|
|
60
|
+
}
|
|
61
|
+
static getProperties(css) {
|
|
62
|
+
try {
|
|
63
|
+
const rules = $CSS.getRules(css);
|
|
64
|
+
const properties = {};
|
|
65
|
+
for (const rule of rules) {
|
|
66
|
+
if (!rule.selectorText.startsWith('@')) {
|
|
67
|
+
properties[rule.selectorText] = $CSS.getStyleProperties(rule.style);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return properties;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
static fromProperties(style) {
|
|
77
|
+
let css = '';
|
|
78
|
+
for (const selector of Object.keys(style).reverse()) {
|
|
79
|
+
css += `${selector} {\n${Object.keys(style[selector])
|
|
80
|
+
.map((property) => `\t${property}: ${style[selector][property]};`)
|
|
81
|
+
.join('\n')}\n}\n`;
|
|
82
|
+
}
|
|
83
|
+
return css;
|
|
84
|
+
}
|
|
85
|
+
static toStyleAttribute(style) {
|
|
86
|
+
let css = '';
|
|
87
|
+
for (const property in style) {
|
|
88
|
+
css += `${property}: ${style[property]};`;
|
|
89
|
+
}
|
|
90
|
+
return css;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Point } from '@vorplex/core';
|
|
2
|
+
export declare class $Element {
|
|
3
|
+
static getRelativePoint(element: HTMLElement, position: 'top' | 'right' | 'bottom' | 'left', offset?: number): Point;
|
|
4
|
+
static getBounds(element: HTMLElement): {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
topLeft: Point;
|
|
8
|
+
topCenter: Point;
|
|
9
|
+
topRight: Point;
|
|
10
|
+
rightCenter: Point;
|
|
11
|
+
bottomLeft: Point;
|
|
12
|
+
bottomCenter: Point;
|
|
13
|
+
bottomRight: Point;
|
|
14
|
+
leftCenter: Point;
|
|
15
|
+
};
|
|
16
|
+
static addEventListener<T extends Document | HTMLElement, TT extends T extends Document ? DocumentEventMap : HTMLElementEventMap, TTT extends string & keyof TT>(element: T, type: TTT, listener: (event: TT[TTT], listener: {
|
|
17
|
+
remove: () => void;
|
|
18
|
+
}) => any, options?: boolean | AddEventListenerOptions): () => void;
|
|
19
|
+
static getEventProperties(elementTag: string): string[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { $Point } from '@vorplex/core';
|
|
2
|
+
export class $Element {
|
|
3
|
+
static getRelativePoint(element, position, offset = 0) {
|
|
4
|
+
const point = $Point.create();
|
|
5
|
+
if (element) {
|
|
6
|
+
const rect = element.getBoundingClientRect();
|
|
7
|
+
if (position === 'top') {
|
|
8
|
+
point.x = rect.x + rect.width / 2;
|
|
9
|
+
point.y = rect.y - offset;
|
|
10
|
+
}
|
|
11
|
+
else if (position === 'right') {
|
|
12
|
+
point.x = rect.x + rect.width + offset;
|
|
13
|
+
point.y = rect.y + rect.height / 2;
|
|
14
|
+
}
|
|
15
|
+
else if (position === 'bottom') {
|
|
16
|
+
point.x = rect.x + rect.width / 2;
|
|
17
|
+
point.y = rect.y + rect.height + offset;
|
|
18
|
+
}
|
|
19
|
+
else if (position === 'left') {
|
|
20
|
+
point.x = rect.x - offset;
|
|
21
|
+
point.y = rect.y + rect.height / 2;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return point;
|
|
25
|
+
}
|
|
26
|
+
static getBounds(element) {
|
|
27
|
+
const rect = element.getBoundingClientRect();
|
|
28
|
+
return {
|
|
29
|
+
width: rect.width,
|
|
30
|
+
height: rect.height,
|
|
31
|
+
topLeft: $Point.create(rect.x, rect.y),
|
|
32
|
+
topCenter: $Point.create(rect.x + rect.width / 2, rect.y),
|
|
33
|
+
topRight: $Point.create(rect.x + rect.width, rect.y),
|
|
34
|
+
rightCenter: $Point.create(rect.x + rect.width, rect.y + rect.height / 2),
|
|
35
|
+
bottomLeft: $Point.create(rect.x, rect.y + rect.height),
|
|
36
|
+
bottomCenter: $Point.create(rect.x + rect.width / 2, rect.y + rect.height),
|
|
37
|
+
bottomRight: $Point.create(rect.x + rect.width, rect.y + rect.height),
|
|
38
|
+
leftCenter: $Point.create(rect.x, rect.y + rect.height / 2),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
static addEventListener(element, type, listener, options) {
|
|
42
|
+
const remove = () => element.removeEventListener(type, handler);
|
|
43
|
+
const handler = event => listener(event, { remove });
|
|
44
|
+
element.addEventListener(type, handler, options);
|
|
45
|
+
return remove;
|
|
46
|
+
}
|
|
47
|
+
static getEventProperties(elementTag) {
|
|
48
|
+
const target = document.createElement(elementTag);
|
|
49
|
+
const events = [];
|
|
50
|
+
for (const key in target) {
|
|
51
|
+
if (key.startsWith('on') && key.length > 2) {
|
|
52
|
+
events.push(key);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return events.sort();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type FileDialogOptions = {
|
|
2
|
+
mimeTypes?: string[];
|
|
3
|
+
multiple?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare class $FileManager {
|
|
6
|
+
static showDialog(options?: Partial<FileDialogOptions>): Promise<File[]>;
|
|
7
|
+
static readText(file: File): Promise<string>;
|
|
8
|
+
static readBase64(file: File): Promise<string>;
|
|
9
|
+
static readBytes(file: File): Promise<ArrayBuffer>;
|
|
10
|
+
static toObjectUrl(file: File): Promise<string>;
|
|
11
|
+
static download(file: string, content: Blob | string): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export class $FileManager {
|
|
2
|
+
static showDialog(options) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
const input = document.createElement('input');
|
|
5
|
+
input.type = 'file';
|
|
6
|
+
if (options) {
|
|
7
|
+
input.accept = options.mimeTypes?.join(',') ?? '';
|
|
8
|
+
input.multiple = !!options.multiple;
|
|
9
|
+
}
|
|
10
|
+
input.click();
|
|
11
|
+
input.onchange = (event) => resolve(Array.from(event.target.files));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
static readText(file) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const reader = new FileReader();
|
|
17
|
+
reader.onerror = () => reject();
|
|
18
|
+
reader.onload = () => resolve(reader.result);
|
|
19
|
+
reader.readAsText(file);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
static readBase64(file) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const reader = new FileReader();
|
|
25
|
+
reader.onerror = () => reject();
|
|
26
|
+
reader.onloadend = () => resolve(reader.result);
|
|
27
|
+
reader.readAsDataURL(file);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
static readBytes(file) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const reader = new FileReader();
|
|
33
|
+
reader.onerror = () => reject();
|
|
34
|
+
reader.onload = () => resolve(reader.result);
|
|
35
|
+
reader.readAsArrayBuffer(file);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
static async toObjectUrl(file) {
|
|
39
|
+
return URL.createObjectURL(file);
|
|
40
|
+
}
|
|
41
|
+
static download(file, content) {
|
|
42
|
+
const downloadLink = document.createElement('a');
|
|
43
|
+
const blob = content instanceof Blob ? content : new Blob([content], { type: 'text/plain' });
|
|
44
|
+
downloadLink.href = URL.createObjectURL(blob);
|
|
45
|
+
downloadLink.setAttribute('download', file);
|
|
46
|
+
downloadLink.click();
|
|
47
|
+
downloadLink.remove();
|
|
48
|
+
URL.revokeObjectURL(downloadLink.href);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Window {
|
|
3
|
+
showDirectoryPicker: () => Promise<FileSystemDirectoryHandle>;
|
|
4
|
+
}
|
|
5
|
+
function showDirectoryPicker(): Promise<FileSystemDirectoryHandle>;
|
|
6
|
+
}
|
|
7
|
+
export declare class $FileSystem {
|
|
8
|
+
private static handle;
|
|
9
|
+
static open(): Promise<FileSystemDirectoryHandle>;
|
|
10
|
+
static getDirectoryHandle(path: string, options?: {
|
|
11
|
+
create?: boolean;
|
|
12
|
+
}): Promise<FileSystemDirectoryHandle>;
|
|
13
|
+
static getFileHandle(path: string, options?: {
|
|
14
|
+
create?: boolean;
|
|
15
|
+
}): Promise<FileSystemFileHandle>;
|
|
16
|
+
static write(path: string, content: FileSystemWriteChunkType | ReadableStream): Promise<void>;
|
|
17
|
+
static read(path: string): Promise<File>;
|
|
18
|
+
static createFolder(path: string): Promise<void>;
|
|
19
|
+
static delete(path: string): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { $Path } from '@vorplex/core';
|
|
2
|
+
export class $FileSystem {
|
|
3
|
+
static handle;
|
|
4
|
+
static async open() {
|
|
5
|
+
$FileSystem.handle = await window.showDirectoryPicker();
|
|
6
|
+
return $FileSystem.handle;
|
|
7
|
+
}
|
|
8
|
+
static async getDirectoryHandle(path, options) {
|
|
9
|
+
const paths = $Path.absolute(path).split('/');
|
|
10
|
+
if (paths.length === 0)
|
|
11
|
+
return $FileSystem.handle;
|
|
12
|
+
let target = paths.shift();
|
|
13
|
+
let handle = $FileSystem.handle ?? (await $FileSystem.open());
|
|
14
|
+
while (target) {
|
|
15
|
+
handle = await handle.getDirectoryHandle(target, options);
|
|
16
|
+
target = paths.shift();
|
|
17
|
+
}
|
|
18
|
+
return handle;
|
|
19
|
+
}
|
|
20
|
+
static async getFileHandle(path, options) {
|
|
21
|
+
const paths = $Path.absolute(path).split('/');
|
|
22
|
+
if (paths.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
let target = paths.shift();
|
|
25
|
+
let handle = $FileSystem.handle ?? (await $FileSystem.open());
|
|
26
|
+
while (target) {
|
|
27
|
+
if (paths.length > 0)
|
|
28
|
+
handle = await handle.getDirectoryHandle(target, options);
|
|
29
|
+
else
|
|
30
|
+
return await handle.getFileHandle(target, options);
|
|
31
|
+
target = paths.shift();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
static async write(path, content) {
|
|
35
|
+
const handle = await $FileSystem.getFileHandle(path, { create: true });
|
|
36
|
+
const writable = await handle.createWritable();
|
|
37
|
+
if (content instanceof ReadableStream) {
|
|
38
|
+
const reader = content.getReader();
|
|
39
|
+
while (true) {
|
|
40
|
+
const { done, value } = await reader.read();
|
|
41
|
+
if (done)
|
|
42
|
+
break;
|
|
43
|
+
await writable.write(value);
|
|
44
|
+
}
|
|
45
|
+
reader.releaseLock();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await writable.write(content);
|
|
49
|
+
}
|
|
50
|
+
await writable.close();
|
|
51
|
+
}
|
|
52
|
+
static async read(path) {
|
|
53
|
+
const handle = await $FileSystem.getFileHandle(path);
|
|
54
|
+
return await handle.getFile();
|
|
55
|
+
}
|
|
56
|
+
static async createFolder(path) {
|
|
57
|
+
await $FileSystem.getDirectoryHandle(path, { create: true });
|
|
58
|
+
}
|
|
59
|
+
static async delete(path) {
|
|
60
|
+
const directory = await $FileSystem.getDirectoryHandle($Path.getDirectory(path));
|
|
61
|
+
await directory.removeEntry($Path.entryName(path), { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class IndexedDB {
|
|
2
|
+
static getDatabaseInfo(name: string): Promise<IDBDatabaseInfo | undefined>;
|
|
3
|
+
static openDatabase(name: string, version?: number): Promise<IDBDatabase & Disposable>;
|
|
4
|
+
static getDatabaseStores(name: string): Promise<string[]>;
|
|
5
|
+
static createDatabaseStore(databaseName: string, storeName: string): Promise<void>;
|
|
6
|
+
static openStore(databaseName: string, storeName: string, mode?: IDBTransactionMode): Promise<IDBObjectStore & Disposable>;
|
|
7
|
+
static set(databaseName: string, storeName: string, key: string, value: any): Promise<void>;
|
|
8
|
+
static get<T = any>(databaseName: string, storeName: string, key: IDBValidKey | IDBKeyRange): Promise<T | null>;
|
|
9
|
+
static getAllKeys(databaseName: string, storeName: string): Promise<string[] | null>;
|
|
10
|
+
static getAll<T = any>(databaseName: string, storeName: string, key?: IDBValidKey | IDBKeyRange): Promise<T[] | null>;
|
|
11
|
+
static clear(databaseName: string, storeName: string): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export class IndexedDB {
|
|
2
|
+
static async getDatabaseInfo(name) {
|
|
3
|
+
const databases = await indexedDB.databases();
|
|
4
|
+
return databases.find((database) => database.name === name);
|
|
5
|
+
}
|
|
6
|
+
static async openDatabase(name, version) {
|
|
7
|
+
const db = await new Promise((resolve, reject) => {
|
|
8
|
+
const request = indexedDB.open(name, version);
|
|
9
|
+
request.onsuccess = () => resolve(request.result);
|
|
10
|
+
request.onerror = (event) => reject(event.target.error);
|
|
11
|
+
});
|
|
12
|
+
return Object.assign(db, {
|
|
13
|
+
[Symbol.dispose]() {
|
|
14
|
+
db.close();
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
static async getDatabaseStores(name) {
|
|
19
|
+
using database = await IndexedDB.openDatabase(name);
|
|
20
|
+
return Array.from(database.objectStoreNames);
|
|
21
|
+
}
|
|
22
|
+
static async createDatabaseStore(databaseName, storeName) {
|
|
23
|
+
const existingStores = await IndexedDB.getDatabaseStores(databaseName);
|
|
24
|
+
if (existingStores.find((store) => store === storeName))
|
|
25
|
+
return;
|
|
26
|
+
const databaseInfo = await IndexedDB.getDatabaseInfo(databaseName);
|
|
27
|
+
const currentVersion = databaseInfo?.version || 1;
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const request = indexedDB.open(databaseName, currentVersion + 1);
|
|
30
|
+
request.onupgradeneeded = () => {
|
|
31
|
+
const db = request.result;
|
|
32
|
+
if (!db.objectStoreNames.contains(storeName)) {
|
|
33
|
+
db.createObjectStore(storeName);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
request.onsuccess = () => {
|
|
37
|
+
request.result.close();
|
|
38
|
+
resolve();
|
|
39
|
+
};
|
|
40
|
+
request.onerror = (event) => reject(event.target.error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
static async openStore(databaseName, storeName, mode = 'readwrite') {
|
|
44
|
+
await IndexedDB.createDatabaseStore(databaseName, storeName);
|
|
45
|
+
const db = await IndexedDB.openDatabase(databaseName);
|
|
46
|
+
const transaction = db.transaction(storeName, mode);
|
|
47
|
+
const objectStore = transaction.objectStore(storeName);
|
|
48
|
+
return Object.assign(objectStore, {
|
|
49
|
+
[Symbol.dispose]() {
|
|
50
|
+
db.close();
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
static async set(databaseName, storeName, key, value) {
|
|
55
|
+
using store = await IndexedDB.openStore(databaseName, storeName);
|
|
56
|
+
const request = store.put(value, key);
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
request.onsuccess = () => resolve();
|
|
59
|
+
request.onerror = (event) => reject(event.target.error);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
static async get(databaseName, storeName, key) {
|
|
63
|
+
using store = await IndexedDB.openStore(databaseName, storeName, 'readonly');
|
|
64
|
+
const request = store.get(key);
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
67
|
+
request.onerror = (event) => reject(event.target.error);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
static async getAllKeys(databaseName, storeName) {
|
|
71
|
+
using store = await IndexedDB.openStore(databaseName, storeName, 'readonly');
|
|
72
|
+
const request = store.getAllKeys();
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
75
|
+
request.onerror = (event) => reject(event.target.error);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
static async getAll(databaseName, storeName, key) {
|
|
79
|
+
using store = await IndexedDB.openStore(databaseName, storeName, 'readonly');
|
|
80
|
+
const request = store.getAll(key);
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
83
|
+
request.onerror = (event) => reject(event.target.error);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
static async clear(databaseName, storeName) {
|
|
87
|
+
using store = await IndexedDB.openStore(databaseName, storeName);
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const request = store.clear();
|
|
90
|
+
request.onsuccess = () => resolve();
|
|
91
|
+
request.onerror = (event) => reject(event.target.error);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vorplex/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"main": "./dist/index.js",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"tslib": "^2.8.1",
|
|
19
|
-
"@vorplex/core": "0.0.
|
|
19
|
+
"@vorplex/core": "0.0.11"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/jest": "^29.5.2",
|