@basmilius/common 3.0.0
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/README.md +19 -0
- package/dist/composable/index.d.ts +16 -0
- package/dist/composable/useCopy.d.ts +2 -0
- package/dist/composable/useDebounced.d.ts +1 -0
- package/dist/composable/useDtoForm.d.ts +2 -0
- package/dist/composable/useInterval.d.ts +2 -0
- package/dist/composable/useLoaded.d.ts +6 -0
- package/dist/composable/useLoadedAction.d.ts +2 -0
- package/dist/composable/useLocalFile.d.ts +1 -0
- package/dist/composable/useMounted.d.ts +2 -0
- package/dist/composable/useNamedRoute.d.ts +2 -0
- package/dist/composable/useNavigate.d.ts +5 -0
- package/dist/composable/usePagination.d.ts +1 -0
- package/dist/composable/usePasswordStrength.d.ts +2 -0
- package/dist/composable/useRouteMeta.d.ts +5 -0
- package/dist/composable/useRouteNames.d.ts +1 -0
- package/dist/composable/useRouteParam.d.ts +2 -0
- package/dist/composable/useService.d.ts +6 -0
- package/dist/error/ForbiddenException.d.ts +1 -0
- package/dist/error/HandledException.d.ts +1 -0
- package/dist/error/UnauthorizedException.d.ts +1 -0
- package/dist/error/index.d.ts +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +267 -0
- package/dist/store/defineStore.d.ts +8 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/util/emptyNull.d.ts +1 -0
- package/dist/util/generateColorPalette.d.ts +1 -0
- package/dist/util/guarded.d.ts +3 -0
- package/dist/util/index.d.ts +9 -0
- package/dist/util/onError.d.ts +1 -0
- package/dist/util/onErrorSnackbar.d.ts +2 -0
- package/dist/util/persistentRef.d.ts +4 -0
- package/dist/util/persistentStringRef.d.ts +2 -0
- package/dist/util/runBefore.d.ts +1 -0
- package/dist/util/unrefAll.d.ts +3 -0
- package/package.json +66 -0
- package/src/composable/index.ts +16 -0
- package/src/composable/useCopy.ts +8 -0
- package/src/composable/useDebounced.ts +5 -0
- package/src/composable/useDtoForm.ts +17 -0
- package/src/composable/useInterval.ts +22 -0
- package/src/composable/useLoaded.ts +38 -0
- package/src/composable/useLoadedAction.ts +13 -0
- package/src/composable/useLocalFile.ts +40 -0
- package/src/composable/useMounted.ts +9 -0
- package/src/composable/useNamedRoute.ts +17 -0
- package/src/composable/useNavigate.ts +28 -0
- package/src/composable/usePagination.ts +35 -0
- package/src/composable/usePasswordStrength.ts +67 -0
- package/src/composable/useRouteMeta.ts +34 -0
- package/src/composable/useRouteNames.ts +14 -0
- package/src/composable/useRouteParam.ts +13 -0
- package/src/composable/useService.ts +20 -0
- package/src/error/ForbiddenException.ts +2 -0
- package/src/error/HandledException.ts +2 -0
- package/src/error/UnauthorizedException.ts +2 -0
- package/src/error/index.ts +3 -0
- package/src/index.ts +4 -0
- package/src/store/defineStore.ts +31 -0
- package/src/store/index.ts +7 -0
- package/src/util/emptyNull.ts +3 -0
- package/src/util/generateColorPalette.ts +132 -0
- package/src/util/guarded.ts +73 -0
- package/src/util/index.ts +9 -0
- package/src/util/onError.ts +5 -0
- package/src/util/onErrorSnackbar.ts +15 -0
- package/src/util/persistentRef.ts +21 -0
- package/src/util/persistentStringRef.ts +6 -0
- package/src/util/runBefore.ts +8 -0
- package/src/util/unrefAll.ts +19 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { converter, formatHex } from 'culori';
|
|
2
|
+
|
|
3
|
+
type Shade =
|
|
4
|
+
| 25
|
|
5
|
+
| 50
|
|
6
|
+
| 100
|
|
7
|
+
| 200
|
|
8
|
+
| 300
|
|
9
|
+
| 400
|
|
10
|
+
| 500
|
|
11
|
+
| 600
|
|
12
|
+
| 700
|
|
13
|
+
| 800
|
|
14
|
+
| 900
|
|
15
|
+
| 950;
|
|
16
|
+
|
|
17
|
+
const toOklch = converter('oklch');
|
|
18
|
+
|
|
19
|
+
const SHADES: Shade[] = [25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
|
|
20
|
+
|
|
21
|
+
const POSITION: Record<Shade, number> = {
|
|
22
|
+
25: 0.00,
|
|
23
|
+
50: 0.05,
|
|
24
|
+
100: 0.10,
|
|
25
|
+
200: 0.20,
|
|
26
|
+
300: 0.35,
|
|
27
|
+
400: 0.48,
|
|
28
|
+
500: 0.55,
|
|
29
|
+
600: 0.65,
|
|
30
|
+
700: 0.75,
|
|
31
|
+
800: 0.85,
|
|
32
|
+
900: 0.93,
|
|
33
|
+
950: 0.98
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function clamp(v: number, min = 0, max = 1) {
|
|
37
|
+
return Math.min(max, Math.max(min, v));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default function (baseHex: string, prefix = 'color') {
|
|
41
|
+
const base = toOklch(baseHex);
|
|
42
|
+
|
|
43
|
+
if (!base || base.mode !== 'oklch') {
|
|
44
|
+
throw new Error('Invalid color');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const palette: Record<string, string> = {};
|
|
48
|
+
|
|
49
|
+
for (const shade of SHADES) {
|
|
50
|
+
const p = POSITION[shade];
|
|
51
|
+
|
|
52
|
+
let l =
|
|
53
|
+
shade < 500
|
|
54
|
+
? base.l + (1 - base.l) * (1 - p / POSITION[500])
|
|
55
|
+
: base.l * (1 - (p - POSITION[500]) / (1 - POSITION[500]));
|
|
56
|
+
|
|
57
|
+
l = clamp(l);
|
|
58
|
+
|
|
59
|
+
const distanceFrom500 = Math.abs(p - POSITION[500]);
|
|
60
|
+
const chromaFalloff = 1 - distanceFrom500 * 1.4;
|
|
61
|
+
|
|
62
|
+
const c = (base.c ?? 0) * clamp(chromaFalloff, 0.2, 1);
|
|
63
|
+
|
|
64
|
+
palette[`--${prefix}-${shade}`] = formatHex({
|
|
65
|
+
mode: 'oklch',
|
|
66
|
+
l,
|
|
67
|
+
c,
|
|
68
|
+
h: base.h
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return palette;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Tailwind Like
|
|
76
|
+
// import { converter, formatHex } from 'culori';
|
|
77
|
+
// import { clamp } from 'lodash-es';
|
|
78
|
+
//
|
|
79
|
+
// type Shade =
|
|
80
|
+
// | 25 | 50 | 100 | 200 | 300 | 400
|
|
81
|
+
// | 500 | 600 | 700 | 800 | 900 | 950;
|
|
82
|
+
//
|
|
83
|
+
// const toOklch = converter('oklch');
|
|
84
|
+
//
|
|
85
|
+
// const SHADES: Shade[] = [
|
|
86
|
+
// 25, 50, 100, 200, 300, 400,
|
|
87
|
+
// 500,
|
|
88
|
+
// 600, 700, 800, 900, 950
|
|
89
|
+
// ];
|
|
90
|
+
//
|
|
91
|
+
// // Perceptual lightness curve (similar to Tailwind gray)
|
|
92
|
+
// const LIGHTNESS_CURVE: Record<Shade, number> = {
|
|
93
|
+
// 25: 0.99,
|
|
94
|
+
// 50: 0.96,
|
|
95
|
+
// 100: 0.92,
|
|
96
|
+
// 200: 0.86,
|
|
97
|
+
// 300: 0.78,
|
|
98
|
+
// 400: 0.66,
|
|
99
|
+
// 500: 0.55,
|
|
100
|
+
// 600: 0.44,
|
|
101
|
+
// 700: 0.33,
|
|
102
|
+
// 800: 0.23,
|
|
103
|
+
// 900: 0.15,
|
|
104
|
+
// 950: 0.08
|
|
105
|
+
// };
|
|
106
|
+
//
|
|
107
|
+
// export default function (baseHex: string, prefix = 'color', maxChroma = 0.08) {
|
|
108
|
+
// const base = toOklch(baseHex);
|
|
109
|
+
//
|
|
110
|
+
// if (!base || base.mode !== 'oklch') {
|
|
111
|
+
// throw new Error('Invalid color');
|
|
112
|
+
// }
|
|
113
|
+
//
|
|
114
|
+
// const palette: Record<string, string> = {};
|
|
115
|
+
//
|
|
116
|
+
// for (const shade of SHADES) {
|
|
117
|
+
// let l = LIGHTNESS_CURVE[shade];
|
|
118
|
+
//
|
|
119
|
+
// const distanceFromMiddle = Math.abs(shade === 500 ? 0 : (shade - 500) / 450);
|
|
120
|
+
// const c = clamp((base.c ?? 0) * maxChroma * (1 - distanceFromMiddle), 0, 1);
|
|
121
|
+
//
|
|
122
|
+
// palette[`--${prefix}-${shade}`] = formatHex({
|
|
123
|
+
// mode: 'oklch',
|
|
124
|
+
// l,
|
|
125
|
+
// c,
|
|
126
|
+
// h: base.h
|
|
127
|
+
// });
|
|
128
|
+
// }
|
|
129
|
+
//
|
|
130
|
+
// return palette;
|
|
131
|
+
// }
|
|
132
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isRequestError, isUnsanctionedRequest } from '@basmilius/http-client';
|
|
2
|
+
import { showSnackbar } from '@flux-ui/components';
|
|
3
|
+
import { ForbiddenException, HandledException, UnauthorizedException } from '../error';
|
|
4
|
+
|
|
5
|
+
const ORIGINAL = Symbol();
|
|
6
|
+
|
|
7
|
+
export default function <T extends Function>(fn: T): T;
|
|
8
|
+
export default function <T extends Function>(fn: T, onError: (err: Error) => void): T;
|
|
9
|
+
export default function <T extends Function>(fn: T, snackbarTitle: string, snackbarMessage: string): T;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Adds basic global error checking on the provided function. Should
|
|
13
|
+
* be used when fetching remote data. Don't use try-catch with this
|
|
14
|
+
* function, because it throws exceptions that our global error handler
|
|
15
|
+
* will catch for you.
|
|
16
|
+
*
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const {getOrder} = useService(OrderService);
|
|
19
|
+
* const _getOrder = guarded(getOrder);
|
|
20
|
+
* const _getOrder = guarded(getOrder, err => console.error(err));
|
|
21
|
+
* const _getOrder = guarded(getOrder, 'Error snackbar title', 'Error snackbar message');
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export default function <T extends Function>(fn: T, onErrorOrSnackbarTitle?: ((err: Error) => void) | string, snackbarMessage?: string): T {
|
|
25
|
+
if (isGuarded(fn)) {
|
|
26
|
+
// note(Bas): Always wrap the original function, so we don't get
|
|
27
|
+
// double error handling.
|
|
28
|
+
fn = fn[ORIGINAL];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const wrapped = async (...args: any[]): Promise<T> => {
|
|
32
|
+
try {
|
|
33
|
+
return await fn(...args);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (isRequestError(err) && err.statusCode === 403) {
|
|
36
|
+
throw new ForbiddenException();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isUnsanctionedRequest(err)) {
|
|
40
|
+
throw new UnauthorizedException();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (onErrorOrSnackbarTitle) {
|
|
44
|
+
if (typeof onErrorOrSnackbarTitle === 'function') {
|
|
45
|
+
onErrorOrSnackbarTitle(err as Error);
|
|
46
|
+
} else {
|
|
47
|
+
showSnackbar({
|
|
48
|
+
color: 'danger',
|
|
49
|
+
icon: 'circle-exclamation',
|
|
50
|
+
message: snackbarMessage,
|
|
51
|
+
title: onErrorOrSnackbarTitle
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new HandledException();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
wrapped[ORIGINAL] = fn;
|
|
63
|
+
|
|
64
|
+
return wrapped as unknown as T;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isGuarded<T extends Function>(fn: T | Guarded<T>): fn is Guarded<T> {
|
|
68
|
+
return ORIGINAL in fn;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type Guarded<T extends Function> = Function & {
|
|
72
|
+
readonly [ORIGINAL]: T;
|
|
73
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as emptyNull } from './emptyNull';
|
|
2
|
+
export { default as generateColorPalette } from './generateColorPalette';
|
|
3
|
+
export { default as guarded } from './guarded';
|
|
4
|
+
export { default as onError } from './onError';
|
|
5
|
+
export { default as onErrorSnackbar } from './onErrorSnackbar';
|
|
6
|
+
export { default as persistentRef } from './persistentRef';
|
|
7
|
+
export { default as persistentStringRef } from './persistentStringRef';
|
|
8
|
+
export { default as runBefore } from './runBefore';
|
|
9
|
+
export { default as unrefAll } from './unrefAll';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isRequestError } from '@basmilius/http-client';
|
|
2
|
+
import { showSnackbar } from '@flux-ui/components';
|
|
3
|
+
import onError from './onError';
|
|
4
|
+
|
|
5
|
+
export default <T extends Function>() => onError<T>(err => {
|
|
6
|
+
if (!isRequestError(err)) {
|
|
7
|
+
throw err;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
showSnackbar({
|
|
11
|
+
color: 'danger',
|
|
12
|
+
icon: 'circle-exclamation',
|
|
13
|
+
message: err.errorDescription
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ref, type Ref, watch } from 'vue';
|
|
2
|
+
|
|
3
|
+
type Deserializer<T> = (value: string) => T;
|
|
4
|
+
type Serializer<T> = (value: T) => string;
|
|
5
|
+
|
|
6
|
+
export default function <T>(key: string, defaultValue: T, serialize: Serializer<T> = JSON.stringify, deserialize: Deserializer<T> = JSON.parse): Ref<T | null> {
|
|
7
|
+
const storedValue = localStorage.getItem(key);
|
|
8
|
+
const initialValue = storedValue ? deserialize(storedValue) : defaultValue;
|
|
9
|
+
|
|
10
|
+
const persistentRef: Ref<T | null> = ref<T>(initialValue) as Ref<T | null>;
|
|
11
|
+
|
|
12
|
+
watch(persistentRef, value => {
|
|
13
|
+
if (value === null || value === undefined) {
|
|
14
|
+
localStorage.removeItem(key);
|
|
15
|
+
} else {
|
|
16
|
+
localStorage.setItem(key, serialize(value));
|
|
17
|
+
}
|
|
18
|
+
}, {deep: true, immediate: true});
|
|
19
|
+
|
|
20
|
+
return persistentRef;
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import persistentRef from './persistentRef';
|
|
3
|
+
|
|
4
|
+
export default function (key: string, defaultValue: string | null): Ref<string | null> {
|
|
5
|
+
return persistentRef(key, defaultValue, value => value ?? '', value => value === '' ? null : value);
|
|
6
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Ref, unref } from 'vue';
|
|
2
|
+
|
|
3
|
+
type UnrefAll<T extends readonly unknown[]> = {
|
|
4
|
+
[K in keyof T]: T[K] extends Ref<infer U> ? NonNullable<U> : T[K];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default function <T extends readonly unknown[]>(...deps: T): UnrefAll<T> {
|
|
8
|
+
return deps
|
|
9
|
+
.map(unref)
|
|
10
|
+
.map(ensure) as UnrefAll<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ensure<T>(dep: T): T {
|
|
14
|
+
if (!dep) {
|
|
15
|
+
throw new Error('Dep is null or undefined.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return dep;
|
|
19
|
+
}
|