@streamscloud/kit 0.0.1-1770364570820
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/core/continuation-token.d.ts +10 -0
- package/dist/core/continuation-token.js +32 -0
- package/dist/core/css/index.d.ts +1 -0
- package/dist/core/css/index.js +1 -0
- package/dist/core/css/style-functions.d.ts +5 -0
- package/dist/core/css/style-functions.js +12 -0
- package/dist/core/cursor-result.d.ts +9 -0
- package/dist/core/cursor-result.js +1 -0
- package/dist/core/data-loaders/cursor-data-loader-with-search.svelte.d.ts +19 -0
- package/dist/core/data-loaders/cursor-data-loader-with-search.svelte.js +57 -0
- package/dist/core/data-loaders/cursor-data-loader.svelte.d.ts +13 -0
- package/dist/core/data-loaders/cursor-data-loader.svelte.js +33 -0
- package/dist/core/data-loaders/data-loader.d.ts +4 -0
- package/dist/core/data-loaders/data-loader.js +1 -0
- package/dist/core/data-loaders/index.d.ts +4 -0
- package/dist/core/data-loaders/index.js +4 -0
- package/dist/core/data-loaders/page-data-loader.svelte.d.ts +15 -0
- package/dist/core/data-loaders/page-data-loader.svelte.js +37 -0
- package/dist/core/deferred.d.ts +6 -0
- package/dist/core/deferred.js +13 -0
- package/dist/core/event-dispatcher.d.ts +9 -0
- package/dist/core/event-dispatcher.js +28 -0
- package/dist/core/files/base64-helper.d.ts +4 -0
- package/dist/core/files/base64-helper.js +22 -0
- package/dist/core/files/blob-storage.d.ts +1 -0
- package/dist/core/files/blob-storage.js +19 -0
- package/dist/core/files/file-helper.d.ts +6 -0
- package/dist/core/files/file-helper.js +27 -0
- package/dist/core/files/file-service.d.ts +4 -0
- package/dist/core/files/file-service.js +54 -0
- package/dist/core/files/file-types.d.ts +16 -0
- package/dist/core/files/file-types.js +28 -0
- package/dist/core/files/file-with-blob-data-helper.d.ts +19 -0
- package/dist/core/files/file-with-blob-data-helper.js +86 -0
- package/dist/core/files/files-provider.d.ts +8 -0
- package/dist/core/files/files-provider.js +38 -0
- package/dist/core/files/image-resizer.d.ts +31 -0
- package/dist/core/files/image-resizer.js +92 -0
- package/dist/core/files/index.d.ts +9 -0
- package/dist/core/files/index.js +9 -0
- package/dist/core/files/types.d.ts +12 -0
- package/dist/core/files/types.js +10 -0
- package/dist/core/graphql.d.ts +4 -0
- package/dist/core/graphql.js +10 -0
- package/dist/core/handle-generator.d.ts +1 -0
- package/dist/core/handle-generator.js +32 -0
- package/dist/core/i18n/index.d.ts +1 -0
- package/dist/core/i18n/index.js +1 -0
- package/dist/core/i18n/plural.d.ts +14 -0
- package/dist/core/i18n/plural.js +18 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/repository/index.d.ts +2 -0
- package/dist/core/repository/index.js +2 -0
- package/dist/core/repository/map-container.svelte.d.ts +9 -0
- package/dist/core/repository/map-container.svelte.js +22 -0
- package/dist/core/repository/repository-notifier.d.ts +19 -0
- package/dist/core/repository/repository-notifier.js +88 -0
- package/dist/core/repository/repository.svelte.d.ts +2 -0
- package/dist/core/repository/repository.svelte.js +59 -0
- package/dist/core/repository/types.d.ts +15 -0
- package/dist/core/repository/types.js +1 -0
- package/dist/core/theme/app-theme.svelte.d.ts +15 -0
- package/dist/core/theme/app-theme.svelte.js +48 -0
- package/dist/core/theme/index.d.ts +4 -0
- package/dist/core/theme/index.js +3 -0
- package/dist/core/theme/theme-cookie.d.ts +5 -0
- package/dist/core/theme/theme-cookie.js +19 -0
- package/dist/core/theme/types.d.ts +5 -0
- package/dist/core/theme/types.js +8 -0
- package/dist/core/toastr/index.d.ts +3 -0
- package/dist/core/toastr/index.js +2 -0
- package/dist/core/toastr/toaster-host.svelte.d.ts +5 -0
- package/dist/core/toastr/toaster-host.svelte.js +40 -0
- package/dist/core/toastr/toastr.scss +27 -0
- package/dist/core/toastr/toastr.svelte.d.ts +8 -0
- package/dist/core/toastr/toastr.svelte.js +27 -0
- package/dist/core/toastr/types.d.ts +14 -0
- package/dist/core/toastr/types.js +1 -0
- package/dist/core/transitions/index.d.ts +1 -0
- package/dist/core/transitions/index.js +1 -0
- package/dist/core/transitions/slide-horizontally.d.ts +8 -0
- package/dist/core/transitions/slide-horizontally.js +54 -0
- package/dist/core/utils/array-helper.d.ts +21 -0
- package/dist/core/utils/array-helper.js +130 -0
- package/dist/core/utils/base64-serializer.d.ts +4 -0
- package/dist/core/utils/base64-serializer.js +21 -0
- package/dist/core/utils/browser.d.ts +1 -0
- package/dist/core/utils/browser.js +1 -0
- package/dist/core/utils/date-helper.d.ts +51 -0
- package/dist/core/utils/date-helper.js +244 -0
- package/dist/core/utils/dom-helper.d.ts +10 -0
- package/dist/core/utils/dom-helper.js +34 -0
- package/dist/core/utils/href-validator.d.ts +22 -0
- package/dist/core/utils/href-validator.js +52 -0
- package/dist/core/utils/html-helper.d.ts +12 -0
- package/dist/core/utils/html-helper.js +104 -0
- package/dist/core/utils/index.d.ts +13 -0
- package/dist/core/utils/index.js +13 -0
- package/dist/core/utils/lazy-init.d.ts +1 -0
- package/dist/core/utils/lazy-init.js +7 -0
- package/dist/core/utils/number-helper.d.ts +5 -0
- package/dist/core/utils/number-helper.js +32 -0
- package/dist/core/utils/string-generator.d.ts +2 -0
- package/dist/core/utils/string-generator.js +5 -0
- package/dist/core/utils/string-helper.d.ts +12 -0
- package/dist/core/utils/string-helper.js +75 -0
- package/dist/core/utils/url-helper.d.ts +3 -0
- package/dist/core/utils/url-helper.js +13 -0
- package/dist/core/utils/utils.d.ts +29 -0
- package/dist/core/utils/utils.js +108 -0
- package/dist/core/validation/form-validation-handler/form-validation-handler.svelte.d.ts +47 -0
- package/dist/core/validation/form-validation-handler/form-validation-handler.svelte.js +182 -0
- package/dist/core/validation/form-validation-handler/index.d.ts +4 -0
- package/dist/core/validation/form-validation-handler/index.js +3 -0
- package/dist/core/validation/form-validation-handler/stub-form-validator.d.ts +5 -0
- package/dist/core/validation/form-validation-handler/stub-form-validator.js +12 -0
- package/dist/core/validation/form-validation-handler/types.d.ts +10 -0
- package/dist/core/validation/form-validation-handler/types.js +1 -0
- package/dist/core/validation/form-validation-handler/yup-form-validator.d.ts +11 -0
- package/dist/core/validation/form-validation-handler/yup-form-validator.js +49 -0
- package/dist/core/validation/form-validator.svelte.d.ts +12 -0
- package/dist/core/validation/form-validator.svelte.js +21 -0
- package/dist/core/validation/i-validator.d.ts +6 -0
- package/dist/core/validation/i-validator.js +1 -0
- package/dist/core/validation/index.d.ts +5 -0
- package/dist/core/validation/index.js +4 -0
- package/dist/core/validation/validation-schemas/email-validation.d.ts +3 -0
- package/dist/core/validation/validation-schemas/email-validation.js +17 -0
- package/dist/core/validation/validation-schemas/handle-validations.d.ts +3 -0
- package/dist/core/validation/validation-schemas/handle-validations.js +16 -0
- package/dist/core/validation/validation-schemas/index.d.ts +6 -0
- package/dist/core/validation/validation-schemas/index.js +5 -0
- package/dist/core/validation/validation-schemas/number-validations.d.ts +4 -0
- package/dist/core/validation/validation-schemas/number-validations.js +30 -0
- package/dist/core/validation/validation-schemas/text-validations.d.ts +4 -0
- package/dist/core/validation/validation-schemas/text-validations.js +19 -0
- package/dist/core/validation/validation-schemas/types.d.ts +19 -0
- package/dist/core/validation/validation-schemas/types.js +1 -0
- package/dist/core/validation/validation-schemas/validation-messages.d.ts +11 -0
- package/dist/core/validation/validation-schemas/validation-messages.js +14 -0
- package/dist/core/validation/validators-hub.svelte.d.ts +14 -0
- package/dist/core/validation/validators-hub.svelte.js +51 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +2 -0
- package/package.json +125 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class ContinuationToken {
|
|
2
|
+
canLoadMore: boolean;
|
|
3
|
+
value: string | null;
|
|
4
|
+
private constructor();
|
|
5
|
+
static init(): ContinuationToken;
|
|
6
|
+
static fromPayload(value: string | null): ContinuationToken;
|
|
7
|
+
static preventLoading(): ContinuationToken;
|
|
8
|
+
toString(): string | null;
|
|
9
|
+
toJSON(): string | null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class ContinuationToken {
|
|
2
|
+
canLoadMore;
|
|
3
|
+
value;
|
|
4
|
+
constructor(canLoadMore, value) {
|
|
5
|
+
this.canLoadMore = canLoadMore;
|
|
6
|
+
this.value = value;
|
|
7
|
+
}
|
|
8
|
+
static init() {
|
|
9
|
+
// initial state, no data is loaded so far
|
|
10
|
+
return new ContinuationToken(true, null);
|
|
11
|
+
}
|
|
12
|
+
static fromPayload(value) {
|
|
13
|
+
if (value?.length && value.length > 0 && value.length < 5000) {
|
|
14
|
+
// initialized with server response that there is more data, and value is valid
|
|
15
|
+
return new ContinuationToken(true, value);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// initialized with server response that there is no more data or value is invalid
|
|
19
|
+
return new ContinuationToken(false, null);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static preventLoading() {
|
|
23
|
+
// additional initilizer, to block further loading
|
|
24
|
+
return new ContinuationToken(false, null);
|
|
25
|
+
}
|
|
26
|
+
toString() {
|
|
27
|
+
return this.value;
|
|
28
|
+
}
|
|
29
|
+
toJSON() {
|
|
30
|
+
return this.value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Css } from './style-functions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Css } from './style-functions';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const baseValue = 16;
|
|
2
|
+
export class Css {
|
|
3
|
+
static rem = (value) => {
|
|
4
|
+
return `${Css.remValue(value)}rem`;
|
|
5
|
+
};
|
|
6
|
+
static remValue = (value) => {
|
|
7
|
+
return value / baseValue;
|
|
8
|
+
};
|
|
9
|
+
static em = (value, base = baseValue) => {
|
|
10
|
+
return `${value / base}em`;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ContinuationToken, type CursorResult } from '..';
|
|
2
|
+
import type { IDataLoader } from './data-loader';
|
|
3
|
+
export declare class CursorDataLoaderWithSearch<T> implements IDataLoader<T> {
|
|
4
|
+
items: T[];
|
|
5
|
+
private continuationToken;
|
|
6
|
+
private _searchString;
|
|
7
|
+
private loadPage;
|
|
8
|
+
private deferred;
|
|
9
|
+
private searchStringMinLength;
|
|
10
|
+
constructor(init: {
|
|
11
|
+
loadPage: (continuationToken: ContinuationToken, searchString: string) => Promise<CursorResult<T> | null>;
|
|
12
|
+
searchStringMinLength?: number;
|
|
13
|
+
});
|
|
14
|
+
get searchString(): string;
|
|
15
|
+
loadMore: () => Promise<T[]>;
|
|
16
|
+
reset(): Promise<void>;
|
|
17
|
+
updateSearchString: (searchString: string | null) => void;
|
|
18
|
+
private isSearchStringEffective;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ContinuationToken, Deferred } from '..';
|
|
2
|
+
import { Utils } from '../utils';
|
|
3
|
+
export class CursorDataLoaderWithSearch {
|
|
4
|
+
items = $state.raw([]);
|
|
5
|
+
continuationToken = $state.raw(ContinuationToken.init());
|
|
6
|
+
_searchString = $state.raw('');
|
|
7
|
+
loadPage;
|
|
8
|
+
deferred = null;
|
|
9
|
+
searchStringMinLength = 1;
|
|
10
|
+
constructor(init) {
|
|
11
|
+
this.loadPage = init.loadPage;
|
|
12
|
+
if (init.searchStringMinLength !== undefined) {
|
|
13
|
+
this.searchStringMinLength = init.searchStringMinLength;
|
|
14
|
+
}
|
|
15
|
+
this.updateSearchString = Utils.debounce(this.updateSearchString, 400);
|
|
16
|
+
}
|
|
17
|
+
get searchString() {
|
|
18
|
+
return this._searchString;
|
|
19
|
+
}
|
|
20
|
+
loadMore = async () => {
|
|
21
|
+
if (this.deferred) {
|
|
22
|
+
return this.deferred.promise;
|
|
23
|
+
}
|
|
24
|
+
if (!this.continuationToken.canLoadMore) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
this.deferred = new Deferred();
|
|
28
|
+
const search = this.isSearchStringEffective(this._searchString) ? this._searchString : '';
|
|
29
|
+
let result = await this.loadPage(this.continuationToken, search);
|
|
30
|
+
if (!result) {
|
|
31
|
+
result = { items: [], continuationToken: ContinuationToken.preventLoading() };
|
|
32
|
+
}
|
|
33
|
+
this.continuationToken = result.continuationToken;
|
|
34
|
+
this.items = [...this.items, ...result.items];
|
|
35
|
+
this.deferred.resolve(result.items);
|
|
36
|
+
this.deferred = null;
|
|
37
|
+
return result.items;
|
|
38
|
+
};
|
|
39
|
+
async reset() {
|
|
40
|
+
this.items = [];
|
|
41
|
+
this.continuationToken = ContinuationToken.init();
|
|
42
|
+
await this.loadMore();
|
|
43
|
+
}
|
|
44
|
+
updateSearchString = (searchString) => {
|
|
45
|
+
const newValue = searchString || '';
|
|
46
|
+
if (newValue === this._searchString) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const wasEffective = this.isSearchStringEffective(this._searchString);
|
|
50
|
+
const isEffective = this.isSearchStringEffective(newValue);
|
|
51
|
+
this._searchString = newValue;
|
|
52
|
+
if (wasEffective || isEffective) {
|
|
53
|
+
this.reset();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
isSearchStringEffective = (searchString) => searchString && searchString.length >= this.searchStringMinLength;
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContinuationToken, type CursorResult } from '..';
|
|
2
|
+
import type { IDataLoader } from './data-loader';
|
|
3
|
+
export declare class CursorDataLoader<T> implements IDataLoader<T> {
|
|
4
|
+
items: T[];
|
|
5
|
+
continuationToken: ContinuationToken;
|
|
6
|
+
private loadPage;
|
|
7
|
+
private deferred;
|
|
8
|
+
constructor(init: {
|
|
9
|
+
loadPage: (continuationToken: ContinuationToken) => Promise<CursorResult<T> | null>;
|
|
10
|
+
});
|
|
11
|
+
loadMore: () => Promise<T[]>;
|
|
12
|
+
reset(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ContinuationToken, Deferred } from '..';
|
|
2
|
+
export class CursorDataLoader {
|
|
3
|
+
items = $state.raw([]);
|
|
4
|
+
continuationToken = $state.raw(ContinuationToken.init());
|
|
5
|
+
loadPage;
|
|
6
|
+
deferred = null;
|
|
7
|
+
constructor(init) {
|
|
8
|
+
this.loadPage = init.loadPage;
|
|
9
|
+
}
|
|
10
|
+
loadMore = async () => {
|
|
11
|
+
if (this.deferred) {
|
|
12
|
+
return this.deferred.promise;
|
|
13
|
+
}
|
|
14
|
+
if (!this.continuationToken.canLoadMore) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
this.deferred = new Deferred();
|
|
18
|
+
let result = await this.loadPage(this.continuationToken);
|
|
19
|
+
if (!result) {
|
|
20
|
+
result = { items: [], continuationToken: ContinuationToken.preventLoading() };
|
|
21
|
+
}
|
|
22
|
+
this.continuationToken = result.continuationToken;
|
|
23
|
+
this.items = [...this.items, ...result.items];
|
|
24
|
+
this.deferred.resolve(result.items);
|
|
25
|
+
this.deferred = null;
|
|
26
|
+
return result.items;
|
|
27
|
+
};
|
|
28
|
+
async reset() {
|
|
29
|
+
this.items = [];
|
|
30
|
+
this.continuationToken = ContinuationToken.init();
|
|
31
|
+
await this.loadMore();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IDataLoader } from './data-loader';
|
|
2
|
+
export declare class PageDataLoader<T> implements IDataLoader<T> {
|
|
3
|
+
private _items;
|
|
4
|
+
private canLoadMore;
|
|
5
|
+
private perPage;
|
|
6
|
+
private loadPage;
|
|
7
|
+
private pageNumber;
|
|
8
|
+
constructor(init: {
|
|
9
|
+
loadPage: (page: number, perPage: number) => Promise<T[]>;
|
|
10
|
+
perPage?: number;
|
|
11
|
+
});
|
|
12
|
+
get items(): T[];
|
|
13
|
+
loadMore: () => Promise<T[]>;
|
|
14
|
+
reset: () => Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class PageDataLoader {
|
|
2
|
+
_items = $state.raw([]);
|
|
3
|
+
canLoadMore = $state(true);
|
|
4
|
+
perPage = 20;
|
|
5
|
+
loadPage;
|
|
6
|
+
pageNumber = 1;
|
|
7
|
+
constructor(init) {
|
|
8
|
+
this.loadPage = init.loadPage;
|
|
9
|
+
if (init.perPage) {
|
|
10
|
+
this.perPage = init.perPage;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
get items() {
|
|
14
|
+
return this._items;
|
|
15
|
+
}
|
|
16
|
+
loadMore = async () => {
|
|
17
|
+
if (!this.canLoadMore) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const items = await this.loadPage(this.pageNumber++, this.perPage);
|
|
22
|
+
this.canLoadMore = items.length === this.perPage;
|
|
23
|
+
this._items = [...this._items, ...items];
|
|
24
|
+
return items;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error('PageDataLoader: failed to load page', error);
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
reset = async () => {
|
|
32
|
+
this.pageNumber = 1;
|
|
33
|
+
this._items = [];
|
|
34
|
+
this.canLoadMore = true;
|
|
35
|
+
await this.loadMore();
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class EventDispatcher {
|
|
2
|
+
static dispatch: <T extends AppEvent>(event: T) => void;
|
|
3
|
+
static subscribe: <T extends AppEvent>(eventType: new (...params: never[]) => T, handler: (event: T) => void) => (() => void);
|
|
4
|
+
}
|
|
5
|
+
export declare abstract class AppEvent {
|
|
6
|
+
constructor();
|
|
7
|
+
get classId(): string;
|
|
8
|
+
set classId(value: string);
|
|
9
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { randomNanoid } from './utils/string-generator';
|
|
2
|
+
export class EventDispatcher {
|
|
3
|
+
static dispatch = (event) => {
|
|
4
|
+
const appEvent = new CustomEvent(event.classId, { detail: event });
|
|
5
|
+
document.dispatchEvent(appEvent);
|
|
6
|
+
};
|
|
7
|
+
static subscribe = (eventType, handler) => {
|
|
8
|
+
const handlerFunc = (ev) => {
|
|
9
|
+
handler(ev.detail);
|
|
10
|
+
};
|
|
11
|
+
const classId = new eventType().classId;
|
|
12
|
+
document.addEventListener(classId, handlerFunc);
|
|
13
|
+
return () => document.removeEventListener(classId, handlerFunc);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export class AppEvent {
|
|
17
|
+
constructor() {
|
|
18
|
+
if (!this.classId) {
|
|
19
|
+
this.classId = randomNanoid();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
get classId() {
|
|
23
|
+
return this.constructor.prototype._uniqueClassId;
|
|
24
|
+
}
|
|
25
|
+
set classId(value) {
|
|
26
|
+
this.constructor.prototype._uniqueClassId = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class Base64Helper {
|
|
2
|
+
static getMimeType(base64) {
|
|
3
|
+
if (!base64 || !base64.startsWith('data:')) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
const end = base64.indexOf(';');
|
|
7
|
+
if (end === -1) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return base64.slice(5, end);
|
|
11
|
+
}
|
|
12
|
+
static getBase64Data(base64) {
|
|
13
|
+
if (!base64) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (!base64.startsWith('data:')) {
|
|
17
|
+
return base64;
|
|
18
|
+
}
|
|
19
|
+
const start = base64.indexOf(',');
|
|
20
|
+
return base64.slice(start + 1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const uploadBlob: (sasUrl: string, blob: Blob, onProgress?: (uploaded: number, total: number) => void) => Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const uploadBlob = async (sasUrl, blob, onProgress = () => { }) => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const xhr = new XMLHttpRequest();
|
|
4
|
+
xhr.open('PUT', sasUrl, true);
|
|
5
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
6
|
+
onProgress(event.loaded, event.total);
|
|
7
|
+
});
|
|
8
|
+
xhr.onload = () => {
|
|
9
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
10
|
+
resolve();
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
reject(new Error(xhr.statusText || 'Request failed'));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
xhr.onerror = () => reject(new Error('Network error'));
|
|
17
|
+
xhr.send(blob);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class FileHelper {
|
|
2
|
+
static blobToFile(blob: Blob, fileName: string): File;
|
|
3
|
+
static getFileExtension(fileName: string): string;
|
|
4
|
+
static omitFileExtension(fileName: string): string;
|
|
5
|
+
static truncateFileName(fileName: string, maxNameLength: number | null | undefined): string;
|
|
6
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { StringHelper } from '../utils';
|
|
2
|
+
export class FileHelper {
|
|
3
|
+
static blobToFile(blob, fileName) {
|
|
4
|
+
return new File([blob], fileName, { type: blob.type });
|
|
5
|
+
}
|
|
6
|
+
static getFileExtension(fileName) {
|
|
7
|
+
const name = StringHelper.isUrl(fileName) ? fileName.split('?')[0] : fileName;
|
|
8
|
+
const dotIndex = name.lastIndexOf('.');
|
|
9
|
+
return dotIndex > 0 ? name.slice(dotIndex + 1) : '';
|
|
10
|
+
}
|
|
11
|
+
static omitFileExtension(fileName) {
|
|
12
|
+
if (!fileName) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
const dotIndex = fileName.lastIndexOf('.');
|
|
16
|
+
return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
|
|
17
|
+
}
|
|
18
|
+
static truncateFileName(fileName, maxNameLength) {
|
|
19
|
+
if (!maxNameLength) {
|
|
20
|
+
return fileName;
|
|
21
|
+
}
|
|
22
|
+
const extension = FileHelper.getFileExtension(fileName);
|
|
23
|
+
const nameWithoutExt = FileHelper.omitFileExtension(fileName);
|
|
24
|
+
const truncatedName = nameWithoutExt.length > maxNameLength ? nameWithoutExt.slice(0, maxNameLength) + '..' : nameWithoutExt;
|
|
25
|
+
return extension ? `${truncatedName}.${extension}` : truncatedName;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const fetchFile: (url: string, fileName?: string) => Promise<File | null>;
|
|
2
|
+
export declare const downloadJson: (data: unknown, fileName?: string) => void;
|
|
3
|
+
export declare const downloadFromUrl: (url: string, fileName: string) => Promise<boolean>;
|
|
4
|
+
export declare const downloadBlob: (blob: Blob, fileName: string) => void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
const SANITIZE_FILENAME_REGEX = /[\\/:"*?<>|]/g;
|
|
3
|
+
const sanitizeFilename = (name) => {
|
|
4
|
+
return name.replace(SANITIZE_FILENAME_REGEX, '_');
|
|
5
|
+
};
|
|
6
|
+
export const fetchFile = async (url, fileName) => {
|
|
7
|
+
let validatedUrl;
|
|
8
|
+
try {
|
|
9
|
+
validatedUrl = new URL(url).toString();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
console.error(`Invalid URL: ${url}`);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch(validatedUrl, { cache: 'no-store' });
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
console.error(`Failed to fetch file: ${url} (HTTP ${response.status})`);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const blob = await response.blob();
|
|
22
|
+
const nameFromUrl = url.split('/').pop()?.split('?')[0] || undefined;
|
|
23
|
+
const name = sanitizeFilename(fileName ?? nameFromUrl ?? nanoid(15));
|
|
24
|
+
return new File([blob], name, {
|
|
25
|
+
type: blob.type,
|
|
26
|
+
lastModified: Date.now()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(`Failed to fetch file: ${url}`, error);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export const downloadJson = (data, fileName = 'data.json') => {
|
|
35
|
+
const json = JSON.stringify(data, null, 2);
|
|
36
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
37
|
+
downloadBlob(blob, fileName);
|
|
38
|
+
};
|
|
39
|
+
export const downloadFromUrl = async (url, fileName) => {
|
|
40
|
+
const file = await fetchFile(url, fileName);
|
|
41
|
+
if (!file) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
downloadBlob(file, fileName);
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
export const downloadBlob = (blob, fileName) => {
|
|
48
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
49
|
+
const a = document.createElement('a');
|
|
50
|
+
a.href = blobUrl;
|
|
51
|
+
a.download = sanitizeFilename(fileName);
|
|
52
|
+
a.click();
|
|
53
|
+
URL.revokeObjectURL(blobUrl);
|
|
54
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class AcceptFileType {
|
|
2
|
+
static readonly any = "*/*";
|
|
3
|
+
static readonly image = "image/png,image/jpeg,image/webp";
|
|
4
|
+
static readonly video = "video/mp4";
|
|
5
|
+
static readonly audio = "audio/wav,audio/mpeg,audio/mp4,audio/aac,audio/aacp,audio/flac";
|
|
6
|
+
static readonly file: string;
|
|
7
|
+
static readonly archive = "application/zip";
|
|
8
|
+
static readonly imageOrVideo: string;
|
|
9
|
+
static readonly anyMedia: string;
|
|
10
|
+
static readonly anySupported: string;
|
|
11
|
+
static readonly webAssetImage: string;
|
|
12
|
+
static readonly webAssetVideo = "video/mp4";
|
|
13
|
+
static readonly webAssetImageOrVideo: string;
|
|
14
|
+
static readonly webAssetIcon = "image/png";
|
|
15
|
+
}
|
|
16
|
+
export declare const matchesAcceptedFileTypes: (file: File, acceptedTypesAndExtensions: string) => boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FileHelper } from './file-helper';
|
|
2
|
+
export class AcceptFileType {
|
|
3
|
+
static any = '*/*';
|
|
4
|
+
static image = 'image/png,image/jpeg,image/webp';
|
|
5
|
+
static video = 'video/mp4';
|
|
6
|
+
static audio = 'audio/wav,audio/mpeg,audio/mp4,audio/aac,audio/aacp,audio/flac';
|
|
7
|
+
static file = 'application/pdf,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,' +
|
|
8
|
+
'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,text/csv';
|
|
9
|
+
static archive = 'application/zip';
|
|
10
|
+
static imageOrVideo = [this.image, this.video].join(',');
|
|
11
|
+
static anyMedia = [this.image, this.video, this.audio].join(',');
|
|
12
|
+
static anySupported = [this.image, this.video, this.file].join(',');
|
|
13
|
+
static webAssetImage = [this.image, 'image/svg+xml'].join(',');
|
|
14
|
+
static webAssetVideo = this.video;
|
|
15
|
+
static webAssetImageOrVideo = [this.webAssetImage, this.webAssetVideo].join(',');
|
|
16
|
+
static webAssetIcon = 'image/png';
|
|
17
|
+
}
|
|
18
|
+
export const matchesAcceptedFileTypes = (file, acceptedTypesAndExtensions) => {
|
|
19
|
+
const isValid = AcceptFileType.anySupported.split(',').includes(file.type);
|
|
20
|
+
if (acceptedTypesAndExtensions) {
|
|
21
|
+
const acceptanceArray = acceptedTypesAndExtensions.split(',');
|
|
22
|
+
const extensions = acceptanceArray.filter((x) => x.startsWith('.'));
|
|
23
|
+
const fileExt = '.' + FileHelper.getFileExtension(file.name);
|
|
24
|
+
const fileTypes = acceptanceArray.filter((x) => !x.startsWith('.'));
|
|
25
|
+
return (extensions.includes(fileExt) || fileTypes.includes(file.type)) && isValid;
|
|
26
|
+
}
|
|
27
|
+
return isValid;
|
|
28
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type BlobWithName, type FileWithBlobUrl } from './types';
|
|
2
|
+
type FileGenerationOptions = {
|
|
3
|
+
id?: string;
|
|
4
|
+
maxNameLength?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare class FileWithBlobDataHelper {
|
|
7
|
+
static fromBase64(base64: string, options?: FileGenerationOptions & {
|
|
8
|
+
name?: string;
|
|
9
|
+
}): FileWithBlobUrl;
|
|
10
|
+
static fromBlob(blob: Blob | BlobWithName, options?: FileGenerationOptions): FileWithBlobUrl;
|
|
11
|
+
static fromFile(file: File, options?: FileGenerationOptions): FileWithBlobUrl;
|
|
12
|
+
static fromUrl(url: string, options?: FileGenerationOptions & {
|
|
13
|
+
explicitName?: string;
|
|
14
|
+
}): Promise<FileWithBlobUrl | null>;
|
|
15
|
+
static fromUrlAnonymous(url: string, options?: FileGenerationOptions): Promise<FileWithBlobUrl | null>;
|
|
16
|
+
static dispose(blobUrl: string): void;
|
|
17
|
+
private static ensureFileWithValidName;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Base64Helper } from './base64-helper';
|
|
2
|
+
import { FileHelper } from './file-helper';
|
|
3
|
+
import { fetchFile } from './file-service';
|
|
4
|
+
import { toBlobWithName } from './types';
|
|
5
|
+
import { default as mime } from 'mime';
|
|
6
|
+
import { nanoid } from 'nanoid';
|
|
7
|
+
export class FileWithBlobDataHelper {
|
|
8
|
+
static fromBase64(base64, options) {
|
|
9
|
+
const binStr = window.atob(Base64Helper.getBase64Data(base64));
|
|
10
|
+
const len = binStr.length;
|
|
11
|
+
const arr = new Uint8Array(len);
|
|
12
|
+
for (let i = 0; i < len; i++) {
|
|
13
|
+
arr[i] = binStr.charCodeAt(i);
|
|
14
|
+
}
|
|
15
|
+
let blob = new Blob([arr], { type: Base64Helper.getMimeType(base64) });
|
|
16
|
+
if (options?.name) {
|
|
17
|
+
blob = toBlobWithName(blob, options.name);
|
|
18
|
+
}
|
|
19
|
+
return FileWithBlobDataHelper.fromBlob(blob, options);
|
|
20
|
+
}
|
|
21
|
+
static fromBlob(blob, options) {
|
|
22
|
+
if (blob instanceof File) {
|
|
23
|
+
return FileWithBlobDataHelper.fromFile(blob, options);
|
|
24
|
+
}
|
|
25
|
+
const extension = mime.getExtension(blob.type);
|
|
26
|
+
const fileName = 'name' in blob ? blob.name : `${nanoid(options?.maxNameLength || 15)}.${extension}`;
|
|
27
|
+
const file = FileHelper.blobToFile(blob, fileName);
|
|
28
|
+
return FileWithBlobDataHelper.fromFile(file, options);
|
|
29
|
+
}
|
|
30
|
+
static fromFile(file, options) {
|
|
31
|
+
const id = options?.id || nanoid(options?.maxNameLength || 15);
|
|
32
|
+
const validFile = FileWithBlobDataHelper.ensureFileWithValidName(file, options?.maxNameLength);
|
|
33
|
+
const result = validFile;
|
|
34
|
+
result.blobUrl = URL.createObjectURL(validFile);
|
|
35
|
+
result.id = id;
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
static async fromUrl(url, options) {
|
|
39
|
+
const file = await fetchFile(url, options?.explicitName);
|
|
40
|
+
if (!file) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const maxNameLength = options?.maxNameLength || options?.explicitName?.length;
|
|
44
|
+
return FileWithBlobDataHelper.fromFile(file, { ...options, maxNameLength });
|
|
45
|
+
}
|
|
46
|
+
static async fromUrlAnonymous(url, options) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const img = new Image();
|
|
49
|
+
img.crossOrigin = 'anonymous';
|
|
50
|
+
img.onload = async () => {
|
|
51
|
+
const canvas = document.createElement('canvas');
|
|
52
|
+
canvas.width = img.width;
|
|
53
|
+
canvas.height = img.height;
|
|
54
|
+
const ctx = canvas.getContext('2d');
|
|
55
|
+
if (!ctx) {
|
|
56
|
+
console.error('Failed to get canvas context');
|
|
57
|
+
return resolve(null);
|
|
58
|
+
}
|
|
59
|
+
ctx.drawImage(img, 0, 0);
|
|
60
|
+
const dataUrl = canvas.toDataURL('image/png', 0.7);
|
|
61
|
+
const file = await FileWithBlobDataHelper.fromUrl(dataUrl, options);
|
|
62
|
+
resolve(file);
|
|
63
|
+
};
|
|
64
|
+
img.onerror = () => {
|
|
65
|
+
console.error(`Failed to load image: ${url}`);
|
|
66
|
+
resolve(null);
|
|
67
|
+
};
|
|
68
|
+
img.src = url;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
static dispose(blobUrl) {
|
|
72
|
+
try {
|
|
73
|
+
URL.revokeObjectURL(blobUrl);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
static ensureFileWithValidName(file, maxNameLength) {
|
|
80
|
+
const truncatedName = FileHelper.truncateFileName(file.name, maxNameLength);
|
|
81
|
+
if (truncatedName !== file.name) {
|
|
82
|
+
return FileHelper.blobToFile(file, truncatedName);
|
|
83
|
+
}
|
|
84
|
+
return file;
|
|
85
|
+
}
|
|
86
|
+
}
|