ezfw-core 1.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/components/EzBaseComponent.ts +648 -0
- package/components/EzComponent.ts +89 -0
- package/components/EzInput.module.scss +183 -0
- package/components/EzInput.ts +104 -0
- package/components/EzLabel.ts +22 -0
- package/components/EzOutlet.ts +181 -0
- package/components/HtmlWrapper.ts +305 -0
- package/components/avatar/EzAvatar.module.scss +200 -0
- package/components/avatar/EzAvatar.ts +130 -0
- package/components/badge/EzBadge.module.scss +202 -0
- package/components/badge/EzBadge.ts +77 -0
- package/components/button/EzButton.module.scss +402 -0
- package/components/button/EzButton.ts +175 -0
- package/components/button/EzButtonGroup.ts +48 -0
- package/components/card/EzCard.module.scss +71 -0
- package/components/card/EzCard.ts +120 -0
- package/components/chart/EzBarChart.ts +47 -0
- package/components/chart/EzChart.module.scss +14 -0
- package/components/chart/EzChart.ts +279 -0
- package/components/chart/EzDoughnutChart.ts +47 -0
- package/components/chart/EzLineChart.ts +53 -0
- package/components/checkbox/EzCheckbox.module.scss +145 -0
- package/components/checkbox/EzCheckbox.ts +115 -0
- package/components/dataview/EzDataView.module.scss +115 -0
- package/components/dataview/EzDataView.ts +355 -0
- package/components/dataview/modes/EzDataViewCards.ts +322 -0
- package/components/dataview/modes/EzDataViewGrid.ts +76 -0
- package/components/datepicker/EzDatePicker.module.scss +348 -0
- package/components/datepicker/EzDatePicker.ts +519 -0
- package/components/dialog/EzDialog.module.scss +180 -0
- package/components/dropdown/EzDropdown.module.scss +107 -0
- package/components/dropdown/EzDropdown.ts +235 -0
- package/components/feed/EzActivityFeed.module.scss +90 -0
- package/components/feed/EzActivityFeed.ts +78 -0
- package/components/form/EzForm.ts +364 -0
- package/components/form/EzValidators.test.js +421 -0
- package/components/form/EzValidators.ts +202 -0
- package/components/grid/EzGrid.scss +88 -0
- package/components/grid/EzGrid.ts +1085 -0
- package/components/grid/EzGridContainer.ts +104 -0
- package/components/grid/body/EzGridBody.scss +283 -0
- package/components/grid/body/EzGridBody.ts +549 -0
- package/components/grid/body/EzGridCell.ts +211 -0
- package/components/grid/body/EzGridRow.ts +196 -0
- package/components/grid/filter/EzGridFilters.scss +78 -0
- package/components/grid/filter/EzGridFilters.ts +285 -0
- package/components/grid/footer/EzGridFooter.scss +136 -0
- package/components/grid/footer/EzGridFooter.ts +448 -0
- package/components/grid/header/EzGridHeader.scss +199 -0
- package/components/grid/header/EzGridHeader.ts +430 -0
- package/components/grid/query/EzGridQuery.ts +81 -0
- package/components/grid/state/EzGridColumns.ts +155 -0
- package/components/grid/state/EzGridController.ts +470 -0
- package/components/grid/state/EzGridLifecycle.ts +136 -0
- package/components/grid/state/EzGridNormalizers.test.js +273 -0
- package/components/grid/state/EzGridNormalizers.ts +162 -0
- package/components/grid/state/EzGridParts.ts +233 -0
- package/components/grid/state/EzGridPersistence.ts +140 -0
- package/components/grid/state/EzGridRemote.test.js +573 -0
- package/components/grid/state/EzGridRemote.ts +335 -0
- package/components/grid/state/EzGridSelection.ts +231 -0
- package/components/grid/state/EzGridSort.ts +286 -0
- package/components/grid/title/EzGridActionBar.ts +98 -0
- package/components/grid/title/EzGridTitle.ts +114 -0
- package/components/grid/title/EzGridTitleBar.scss +65 -0
- package/components/grid/title/EzGridTitleBar.ts +87 -0
- package/components/grid/types.ts +607 -0
- package/components/panel/EzPanel.module.scss +133 -0
- package/components/panel/EzPanel.ts +147 -0
- package/components/radio/EzRadio.module.scss +190 -0
- package/components/radio/EzRadio.ts +149 -0
- package/components/select/EzSelect.module.scss +153 -0
- package/components/select/EzSelect.ts +238 -0
- package/components/skeleton/EzSkeleton.module.scss +95 -0
- package/components/skeleton/EzSkeleton.ts +70 -0
- package/components/store/EzStore.ts +344 -0
- package/components/switch/EzSwitch.module.scss +164 -0
- package/components/switch/EzSwitch.ts +117 -0
- package/components/tabs/EzTabPanel.module.scss +181 -0
- package/components/tabs/EzTabPanel.ts +402 -0
- package/components/textarea/EzTextarea.module.scss +131 -0
- package/components/textarea/EzTextarea.ts +161 -0
- package/components/timepicker/EzTimePicker.module.scss +282 -0
- package/components/timepicker/EzTimePicker.ts +540 -0
- package/components/toast/EzToast.module.scss +291 -0
- package/components/tooltip/EzTooltip.module.scss +124 -0
- package/components/tooltip/EzTooltip.ts +153 -0
- package/core/EzComponentTypes.ts +693 -0
- package/core/EzError.ts +63 -0
- package/core/EzModel.ts +268 -0
- package/core/EzTypes.ts +328 -0
- package/core/eventBus.ts +284 -0
- package/core/ez.ts +617 -0
- package/core/loader.ts +725 -0
- package/core/renderer.ts +1010 -0
- package/core/router.ts +490 -0
- package/core/services.ts +124 -0
- package/core/state.ts +142 -0
- package/core/utils.ts +81 -0
- package/package.json +51 -0
- package/services/RouteUI.js +17 -0
- package/services/crypto.js +64 -0
- package/services/dialog.js +222 -0
- package/services/fetchApi.js +63 -0
- package/services/firebase.js +30 -0
- package/services/toast.js +214 -0
- package/template/doc/EzDocs.js +15 -0
- package/template/doc/EzDocs.module.scss +627 -0
- package/template/doc/EzDocsController.js +164 -0
- package/template/doc/data/activityfeed/EzActivityFeedDoc.js +42 -0
- package/template/doc/data/avatar/EzAvatarDoc.js +71 -0
- package/template/doc/data/badge/EzBadgeDoc.js +92 -0
- package/template/doc/data/button/EzButtonDoc.js +77 -0
- package/template/doc/data/buttongroup/EzButtonGroupDoc.js +102 -0
- package/template/doc/data/card/EzCardDoc.js +39 -0
- package/template/doc/data/chart/EzChartDoc.js +60 -0
- package/template/doc/data/checkbox/EzCheckboxDoc.js +67 -0
- package/template/doc/data/component/EzComponentDoc.js +34 -0
- package/template/doc/data/cssmodules/CSSModulesDoc.js +70 -0
- package/template/doc/data/datepicker/EzDatePickerDoc.js +126 -0
- package/template/doc/data/dialog/EzDialogDoc.js +217 -0
- package/template/doc/data/dropdown/EzDropdownDoc.js +178 -0
- package/template/doc/data/form/EzFormDoc.js +90 -0
- package/template/doc/data/grid/EzGridDoc.js +99 -0
- package/template/doc/data/input/EzInputDoc.js +92 -0
- package/template/doc/data/label/EzLabelDoc.js +40 -0
- package/template/doc/data/model/EzModelDoc.js +53 -0
- package/template/doc/data/outlet/EzOutletDoc.js +63 -0
- package/template/doc/data/panel/EzPanelDoc.js +214 -0
- package/template/doc/data/radio/EzRadioDoc.js +174 -0
- package/template/doc/data/router/EzRouterDoc.js +75 -0
- package/template/doc/data/select/EzSelectDoc.js +37 -0
- package/template/doc/data/skeleton/EzSkeletonDoc.js +149 -0
- package/template/doc/data/switch/EzSwitchDoc.js +82 -0
- package/template/doc/data/tabpanel/EzTabPanelDoc.js +44 -0
- package/template/doc/data/textarea/EzTextareaDoc.js +131 -0
- package/template/doc/data/timepicker/EzTimePickerDoc.js +107 -0
- package/template/doc/data/tooltip/EzTooltipDoc.js +193 -0
- package/template/doc/data/validators/EzValidatorsDoc.js +37 -0
- package/template/doc/sidebar/EzDocsSidebar.js +32 -0
- package/template/doc/sidebar/category/EzDocsCategory.js +33 -0
- package/template/doc/sidebar/item/EzDocsComponentItem.js +24 -0
- package/template/doc/viewer/EzDocsViewer.js +18 -0
- package/template/doc/viewer/codepanel/EzDocsCodePanel.js +51 -0
- package/template/doc/viewer/content/EzDocsContent.js +315 -0
- package/template/doc/viewer/header/EzDocsViewerHeader.js +46 -0
- package/template/doc/viewer/showcase/EzDocsShowcase.js +59 -0
- package/template/doc/viewer/showcase/EzDocsShowcaseSection.js +25 -0
- package/template/doc/viewer/showcase/EzDocsVariantItem.js +29 -0
- package/template/doc/welcome/EzDocsWelcome.js +48 -0
- package/themes/ez-theme.scss +179 -0
- package/themes/nature-fresh.scss +169 -0
- package/types/global.d.ts +21 -0
- package/utils/cssModules.js +81 -0
package/core/state.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { deepSignal } from 'deepsignal';
|
|
2
|
+
import { EzError } from './EzError.js';
|
|
3
|
+
|
|
4
|
+
export interface ControllerDefinition {
|
|
5
|
+
state?: Record<string, unknown>;
|
|
6
|
+
getState?: (key?: string) => unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface GridControllerDefinition {
|
|
11
|
+
onInit?: () => void | Promise<void>;
|
|
12
|
+
beforeDataRender?: () => void | Promise<void>;
|
|
13
|
+
afterDataRender?: () => void | Promise<void>;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GridBehaviorDefinition {
|
|
18
|
+
onInit?: () => void | Promise<void>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface EzInstance {
|
|
23
|
+
_controllers: Record<string, ControllerDefinition>;
|
|
24
|
+
_gridControllerDefinitions: Record<string, GridControllerDefinition>;
|
|
25
|
+
_gridBehaviors: Record<string, GridBehaviorDefinition>;
|
|
26
|
+
_loader: {
|
|
27
|
+
resolveController: (name: string) => Promise<void>;
|
|
28
|
+
resolveGridBehavior: (name: string) => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
_context: {
|
|
31
|
+
route?: string;
|
|
32
|
+
view?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class EzState {
|
|
37
|
+
private ez: EzInstance;
|
|
38
|
+
|
|
39
|
+
constructor(ez: EzInstance) {
|
|
40
|
+
this.ez = ez;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defineController(name: string, definition: ControllerDefinition): void {
|
|
44
|
+
definition.state = deepSignal(definition.state || {});
|
|
45
|
+
|
|
46
|
+
definition.getState = (key: string = '') => {
|
|
47
|
+
if (!key) return definition.state;
|
|
48
|
+
return (definition.state as Record<string, unknown>)[key];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.ez._controllers[name] = definition;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getController(name: string): ControllerDefinition | Promise<ControllerDefinition> {
|
|
55
|
+
if (this.ez._controllers[name]) return this.ez._controllers[name];
|
|
56
|
+
|
|
57
|
+
return (async () => {
|
|
58
|
+
await this.ez._loader.resolveController(name);
|
|
59
|
+
return this.ez._controllers[name];
|
|
60
|
+
})();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getControllerSync(name: string): ControllerDefinition {
|
|
64
|
+
const ctrl = this.ez._controllers[name];
|
|
65
|
+
if (!ctrl) {
|
|
66
|
+
throw new EzError({
|
|
67
|
+
code: 'EZ_CONTROLLER_002',
|
|
68
|
+
source: 'controller',
|
|
69
|
+
message: `Controller "${name}" accessed before being resolved`,
|
|
70
|
+
route: this.ez._context.route,
|
|
71
|
+
view: this.ez._context.view
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return ctrl;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
defineGridController(name: string, definition: GridControllerDefinition): void {
|
|
78
|
+
if (!name) {
|
|
79
|
+
throw new EzError({
|
|
80
|
+
code: 'EZ_GRID_CTRL_001',
|
|
81
|
+
message: 'defineGridController requires a "gridController" name'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this.ez._gridControllerDefinitions[name]) {
|
|
86
|
+
throw new EzError({
|
|
87
|
+
code: 'EZ_GRID_CTRL_002',
|
|
88
|
+
message: `GridController "${name}" already defined`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.ez._gridControllerDefinitions[name] = definition;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
defineGridBehavior(name: string, definition: GridBehaviorDefinition): void {
|
|
96
|
+
if (!name) return;
|
|
97
|
+
|
|
98
|
+
this.ez._gridBehaviors[name] = definition;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async getGridBehavior(name: string): Promise<GridBehaviorDefinition | null> {
|
|
102
|
+
if (!name) return null;
|
|
103
|
+
|
|
104
|
+
if (this.ez._gridBehaviors[name]) {
|
|
105
|
+
return this.ez._gridBehaviors[name];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await this.ez._loader.resolveGridBehavior(name);
|
|
109
|
+
|
|
110
|
+
const behavior = this.ez._gridBehaviors[name];
|
|
111
|
+
|
|
112
|
+
if (!behavior) {
|
|
113
|
+
throw new EzError({
|
|
114
|
+
code: 'EZ_GRID_BEHAVIOR_INVALID',
|
|
115
|
+
source: 'grid',
|
|
116
|
+
message: `GridBehavior "${name}" was loaded but did not register itself`,
|
|
117
|
+
route: this.ez._context?.route,
|
|
118
|
+
view: this.ez._context?.view
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return behavior;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getDeepValue(obj: Record<string, unknown>, pathArray: string[]): unknown {
|
|
126
|
+
return pathArray.reduce((acc: unknown, key: string) => {
|
|
127
|
+
if (acc && typeof acc === 'object') {
|
|
128
|
+
return (acc as Record<string, unknown>)[key];
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}, obj);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setDeepValue(obj: Record<string, unknown>, props: string[], value: unknown): void {
|
|
135
|
+
let target: Record<string, unknown> = obj;
|
|
136
|
+
for (let i = 0; i < props.length - 1; i++) {
|
|
137
|
+
const key = props[i];
|
|
138
|
+
target = target[key] as Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
target[props[props.length - 1]] = value;
|
|
141
|
+
}
|
|
142
|
+
}
|
package/core/utils.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type FormatType = 'currency' | 'percentage' | 'number' | 'compact';
|
|
2
|
+
|
|
3
|
+
export interface FormatOptions {
|
|
4
|
+
locale?: string;
|
|
5
|
+
currency?: string;
|
|
6
|
+
decimals?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function format(value: unknown, type?: FormatType, options: FormatOptions = {}): string {
|
|
10
|
+
const { locale = 'en-US', currency = 'USD', decimals } = options;
|
|
11
|
+
|
|
12
|
+
if (value === null || value === undefined) return '';
|
|
13
|
+
if (typeof value !== 'number') return String(value);
|
|
14
|
+
|
|
15
|
+
switch (type) {
|
|
16
|
+
case 'currency':
|
|
17
|
+
return new Intl.NumberFormat(locale, {
|
|
18
|
+
style: 'currency',
|
|
19
|
+
currency,
|
|
20
|
+
minimumFractionDigits: decimals ?? 2,
|
|
21
|
+
maximumFractionDigits: decimals ?? 2
|
|
22
|
+
}).format(value);
|
|
23
|
+
|
|
24
|
+
case 'percentage':
|
|
25
|
+
return new Intl.NumberFormat(locale, {
|
|
26
|
+
style: 'percent',
|
|
27
|
+
minimumFractionDigits: decimals ?? 0,
|
|
28
|
+
maximumFractionDigits: decimals ?? 2
|
|
29
|
+
}).format(value / 100);
|
|
30
|
+
|
|
31
|
+
case 'compact':
|
|
32
|
+
return new Intl.NumberFormat(locale, {
|
|
33
|
+
notation: 'compact',
|
|
34
|
+
compactDisplay: 'short'
|
|
35
|
+
}).format(value);
|
|
36
|
+
|
|
37
|
+
case 'number':
|
|
38
|
+
default:
|
|
39
|
+
return value.toLocaleString(locale, {
|
|
40
|
+
minimumFractionDigits: decimals,
|
|
41
|
+
maximumFractionDigits: decimals
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function capitalize(str: string): string {
|
|
47
|
+
if (!str) return '';
|
|
48
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function capitalizeWords(str: string): string {
|
|
52
|
+
if (!str) return '';
|
|
53
|
+
return str.split(' ').map(word => capitalize(word)).join(' ');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function camelToKebab(str: string): string {
|
|
57
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function kebabToCamel(str: string): string {
|
|
61
|
+
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function truncate(str: string, length: number, suffix = '...'): string {
|
|
65
|
+
if (!str || str.length <= length) return str;
|
|
66
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function pluralize(count: number, singular: string, plural?: string): string {
|
|
70
|
+
return count === 1 ? singular : (plural || singular + 's');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const EzUtils = {
|
|
74
|
+
format,
|
|
75
|
+
capitalize,
|
|
76
|
+
capitalizeWords,
|
|
77
|
+
camelToKebab,
|
|
78
|
+
kebabToCamel,
|
|
79
|
+
truncate,
|
|
80
|
+
pluralize
|
|
81
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ezfw-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Ez Framework - A declarative component framework for building modern web applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./core/ez.ts",
|
|
7
|
+
"module": "./core/ez.ts",
|
|
8
|
+
"types": "./core/ez.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./core/ez.ts",
|
|
12
|
+
"types": "./core/ez.ts"
|
|
13
|
+
},
|
|
14
|
+
"./*": "./*"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"core",
|
|
18
|
+
"components",
|
|
19
|
+
"services",
|
|
20
|
+
"template",
|
|
21
|
+
"themes",
|
|
22
|
+
"types",
|
|
23
|
+
"utils"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ez",
|
|
27
|
+
"framework",
|
|
28
|
+
"component",
|
|
29
|
+
"declarative",
|
|
30
|
+
"vite",
|
|
31
|
+
"typescript"
|
|
32
|
+
],
|
|
33
|
+
"author": "Ez Hard Soft",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://bitbucket.org/ez-hard-soft/ez.git"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@preact/signals": "^2.5.1",
|
|
41
|
+
"deepsignal": "^1.6.0",
|
|
42
|
+
"moment": "^2.30.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"vite": ">=5.0.0",
|
|
46
|
+
"sass": ">=1.69.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class RouteUIService {
|
|
2
|
+
buildBreadcrumbs({ chain, fullPath }) {
|
|
3
|
+
return chain.map(node => {
|
|
4
|
+
const meta = node.routeDef?.meta || {};
|
|
5
|
+
|
|
6
|
+
const label =
|
|
7
|
+
typeof meta.breadcrumb === 'function'
|
|
8
|
+
? meta.breadcrumb(node.params)
|
|
9
|
+
: meta.title || node.view;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
label,
|
|
13
|
+
route: fullPath
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
let _ready = false;
|
|
2
|
+
let _key = null;
|
|
3
|
+
let _sodium = null;
|
|
4
|
+
|
|
5
|
+
export const CryptoService = {
|
|
6
|
+
async init(secret) {
|
|
7
|
+
if (_ready) return;
|
|
8
|
+
|
|
9
|
+
// Dynamic import - libsodium only loads when init() is called
|
|
10
|
+
const { default: sodium } = await import('libsodium-wrappers');
|
|
11
|
+
_sodium = sodium;
|
|
12
|
+
|
|
13
|
+
await _sodium.ready;
|
|
14
|
+
|
|
15
|
+
if (!secret) {
|
|
16
|
+
throw new Error('[CryptoService] secret is required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 🔑 SAME KDF AS SERVER
|
|
20
|
+
_key = _sodium.crypto_generichash(
|
|
21
|
+
_sodium.crypto_secretbox_KEYBYTES,
|
|
22
|
+
secret
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
_ready = true;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
encode(value) {
|
|
29
|
+
if (!_ready) {
|
|
30
|
+
throw new Error('[CryptoService] not initialized');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const nonce = _sodium.randombytes_buf(
|
|
34
|
+
_sodium.crypto_secretbox_NONCEBYTES
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const cipher = _sodium.crypto_secretbox_easy(
|
|
38
|
+
_sodium.from_string(JSON.stringify(value)),
|
|
39
|
+
nonce,
|
|
40
|
+
_key
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
nonce: _sodium.to_base64(nonce),
|
|
45
|
+
cipher: _sodium.to_base64(cipher)
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
decode(payload) {
|
|
50
|
+
if (!_ready) {
|
|
51
|
+
throw new Error('[CryptoService] not initialized');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { nonce, cipher } = JSON.parse(payload);
|
|
55
|
+
|
|
56
|
+
const decrypted = _sodium.crypto_secretbox_open_easy(
|
|
57
|
+
_sodium.from_base64(cipher),
|
|
58
|
+
_sodium.from_base64(nonce),
|
|
59
|
+
_key
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return JSON.parse(_sodium.to_string(decrypted));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
export class EzDialogService {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._stack = [];
|
|
4
|
+
this._idCounter = 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
get stack() {
|
|
8
|
+
return this._stack;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async open(config) {
|
|
12
|
+
const dialogId = `ez-dialog-${++this._idCounter}`;
|
|
13
|
+
let resolvePromise;
|
|
14
|
+
let el;
|
|
15
|
+
|
|
16
|
+
// Create dialog instance with methods
|
|
17
|
+
const dialogInstance = {
|
|
18
|
+
_id: dialogId,
|
|
19
|
+
_el: null,
|
|
20
|
+
_isMaximized: config.maximized || false,
|
|
21
|
+
_isClosed: false,
|
|
22
|
+
_isHidden: false,
|
|
23
|
+
|
|
24
|
+
close: (result = null) => {
|
|
25
|
+
if (dialogInstance._isClosed) return;
|
|
26
|
+
dialogInstance._isClosed = true;
|
|
27
|
+
|
|
28
|
+
document.removeEventListener('keydown', onEscKey);
|
|
29
|
+
|
|
30
|
+
if (dialogInstance._el) {
|
|
31
|
+
dialogInstance._el.classList.remove('visible');
|
|
32
|
+
dialogInstance._el.classList.add('closing');
|
|
33
|
+
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
dialogInstance._el?.remove();
|
|
36
|
+
}, 200);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.pop(dialogInstance);
|
|
40
|
+
|
|
41
|
+
if (resolvePromise) {
|
|
42
|
+
resolvePromise(result);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (config.onClose) {
|
|
46
|
+
config.onClose(result);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
hide: () => {
|
|
51
|
+
if (dialogInstance._isHidden || dialogInstance._isClosed) return;
|
|
52
|
+
dialogInstance._isHidden = true;
|
|
53
|
+
|
|
54
|
+
if (dialogInstance._el) {
|
|
55
|
+
dialogInstance._el.classList.remove('visible');
|
|
56
|
+
dialogInstance._el.classList.add('hidden');
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
show: () => {
|
|
61
|
+
if (!dialogInstance._isHidden || dialogInstance._isClosed) return;
|
|
62
|
+
dialogInstance._isHidden = false;
|
|
63
|
+
|
|
64
|
+
if (dialogInstance._el) {
|
|
65
|
+
dialogInstance._el.classList.remove('hidden');
|
|
66
|
+
dialogInstance._el.classList.add('visible');
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
toggleMaximize: () => {
|
|
71
|
+
dialogInstance._isMaximized = !dialogInstance._isMaximized;
|
|
72
|
+
const dialogEl = dialogInstance._el?.querySelector('[class*="dialog"]');
|
|
73
|
+
if (dialogEl) {
|
|
74
|
+
dialogEl.classList.toggle('maximized', dialogInstance._isMaximized);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
isHidden: () => dialogInstance._isHidden
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ESC key handler
|
|
82
|
+
const onEscKey = (e) => {
|
|
83
|
+
if (e.key === 'Escape' && config.closeOnEsc !== false) {
|
|
84
|
+
if (this.getTopDialog() === dialogInstance) {
|
|
85
|
+
dialogInstance.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Overlay click handler
|
|
91
|
+
const onOverlayClick = (e) => {
|
|
92
|
+
if (config.closeOnOverlay !== false && e.target === el) {
|
|
93
|
+
dialogInstance.close();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Build dialog config
|
|
98
|
+
const dialogConfig = {
|
|
99
|
+
eztype: 'EzDialog',
|
|
100
|
+
props: {
|
|
101
|
+
...config,
|
|
102
|
+
_dialogId: dialogId,
|
|
103
|
+
_dialogInstance: dialogInstance
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Render through framework
|
|
108
|
+
el = await ez._createElement(dialogConfig);
|
|
109
|
+
dialogInstance._el = el;
|
|
110
|
+
|
|
111
|
+
// Set z-index based on stack
|
|
112
|
+
const level = this._stack.length;
|
|
113
|
+
const zIndex = 1000 + (level * 10);
|
|
114
|
+
el.style.zIndex = zIndex;
|
|
115
|
+
|
|
116
|
+
// Add event listeners
|
|
117
|
+
if (config.closeOnEsc !== false) {
|
|
118
|
+
document.addEventListener('keydown', onEscKey);
|
|
119
|
+
}
|
|
120
|
+
if (config.closeOnOverlay !== false) {
|
|
121
|
+
el.addEventListener('click', onOverlayClick);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Push to stack and append to body
|
|
125
|
+
this.push(dialogInstance);
|
|
126
|
+
document.body.appendChild(el);
|
|
127
|
+
|
|
128
|
+
// Trigger animation
|
|
129
|
+
requestAnimationFrame(() => {
|
|
130
|
+
el.classList.add('visible');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Return promise
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
resolvePromise = resolve;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async alert(messageOrConfig, options = {}) {
|
|
140
|
+
const config = typeof messageOrConfig === 'string'
|
|
141
|
+
? { message: messageOrConfig, ...options }
|
|
142
|
+
: messageOrConfig;
|
|
143
|
+
|
|
144
|
+
return this.open({
|
|
145
|
+
title: config.title || 'Alert',
|
|
146
|
+
message: config.message,
|
|
147
|
+
icon: config.icon || 'fa-solid fa-circle-info',
|
|
148
|
+
iconVariant: config.iconVariant || 'info',
|
|
149
|
+
size: config.size || 'sm',
|
|
150
|
+
closable: config.closable ?? true,
|
|
151
|
+
closeOnOverlay: config.closeOnOverlay ?? true,
|
|
152
|
+
buttons: [
|
|
153
|
+
{
|
|
154
|
+
text: config.buttonText || 'OK',
|
|
155
|
+
variant: 'primary',
|
|
156
|
+
value: true
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async confirm(messageOrConfig, options = {}) {
|
|
163
|
+
const config = typeof messageOrConfig === 'string'
|
|
164
|
+
? { message: messageOrConfig, ...options }
|
|
165
|
+
: messageOrConfig;
|
|
166
|
+
|
|
167
|
+
const result = await this.open({
|
|
168
|
+
title: config.title || 'Confirm',
|
|
169
|
+
message: config.message,
|
|
170
|
+
icon: config.icon || 'fa-solid fa-circle-question',
|
|
171
|
+
iconVariant: config.iconVariant || 'warning',
|
|
172
|
+
size: config.size || 'sm',
|
|
173
|
+
closable: config.closable ?? false,
|
|
174
|
+
closeOnOverlay: config.closeOnOverlay ?? false,
|
|
175
|
+
closeOnEsc: config.closeOnEsc ?? true,
|
|
176
|
+
buttons: [
|
|
177
|
+
{
|
|
178
|
+
text: config.cancelText || 'Cancel',
|
|
179
|
+
variant: 'secondary',
|
|
180
|
+
value: false
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
text: config.confirmText || 'Confirm',
|
|
184
|
+
variant: config.confirmVariant || 'primary',
|
|
185
|
+
value: true
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return result === true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async danger(messageOrConfig, options = {}) {
|
|
194
|
+
const config = typeof messageOrConfig === 'string'
|
|
195
|
+
? { message: messageOrConfig, ...options }
|
|
196
|
+
: messageOrConfig;
|
|
197
|
+
|
|
198
|
+
return this.confirm({
|
|
199
|
+
...config,
|
|
200
|
+
title: config.title || 'Are you sure?',
|
|
201
|
+
icon: config.icon || 'fa-solid fa-triangle-exclamation',
|
|
202
|
+
iconVariant: 'danger',
|
|
203
|
+
confirmText: config.confirmText || 'Delete',
|
|
204
|
+
confirmVariant: 'danger'
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
push(dialog) {
|
|
209
|
+
this._stack.push(dialog);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
pop(dialog) {
|
|
213
|
+
const index = this._stack.indexOf(dialog);
|
|
214
|
+
if (index > -1) {
|
|
215
|
+
this._stack.splice(index, 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getTopDialog() {
|
|
220
|
+
return this._stack.length > 0 ? this._stack[this._stack.length - 1] : null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {EzError} from "../core/EzError.js";
|
|
2
|
+
|
|
3
|
+
export class FetchApi {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.baseUrl = config.baseUrl || '';
|
|
6
|
+
this._headersResolver = null;
|
|
7
|
+
this._responseTransformers = [];
|
|
8
|
+
this._errorHandler = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setHeadersResolver(fn) {
|
|
12
|
+
this._headersResolver = fn;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
addResponseTransformer(fn) {
|
|
16
|
+
this._responseTransformers.push(fn);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setErrorHandler(fn) {
|
|
20
|
+
this._errorHandler = fn;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async request(url, options = {}) {
|
|
24
|
+
const res = await fetch(this.baseUrl + url, {
|
|
25
|
+
method: options.method || 'GET',
|
|
26
|
+
headers: this._headersResolver?.() || {},
|
|
27
|
+
body: options.body
|
|
28
|
+
? JSON.stringify(options.body)
|
|
29
|
+
: undefined
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const payload = await res.json().catch(() => null);
|
|
33
|
+
|
|
34
|
+
// 🔴 ERROR PATH (TU CONTRATO REAL)
|
|
35
|
+
if (!res.ok || payload?.success === false) {
|
|
36
|
+
const error = {
|
|
37
|
+
status: payload?.status ?? res.status,
|
|
38
|
+
message: payload?.message ?? 'Unknown error',
|
|
39
|
+
field: payload?.field ?? null
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (typeof this._errorHandler === 'function') {
|
|
43
|
+
await this._errorHandler(error, res);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new EzError({
|
|
47
|
+
code: 'EZ_FETCH_001',
|
|
48
|
+
source: 'fetch',
|
|
49
|
+
message: 'Request failed before reaching server',
|
|
50
|
+
route: ez._context.route,
|
|
51
|
+
cause: error
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 🟢 SUCCESS PATH
|
|
56
|
+
let data = payload;
|
|
57
|
+
for (const transform of this._responseTransformers) {
|
|
58
|
+
data = await transform(data, res);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class FirebaseService {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.config = config;
|
|
4
|
+
this.app = null;
|
|
5
|
+
this.auth = null;
|
|
6
|
+
this.initialized = false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async init() {
|
|
10
|
+
if (this.initialized) return;
|
|
11
|
+
|
|
12
|
+
const { initializeApp } = await import('firebase/app');
|
|
13
|
+
const { getAuth } = await import('firebase/auth');
|
|
14
|
+
|
|
15
|
+
this.app = initializeApp(this.config);
|
|
16
|
+
this.auth = getAuth(this.app);
|
|
17
|
+
this.initialized = true;
|
|
18
|
+
|
|
19
|
+
console.log('[ez] Firebase initialized');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async loginWithEmail(email, password) {
|
|
23
|
+
if (!this.initialized) {
|
|
24
|
+
await this.init();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { signInWithEmailAndPassword } = await import('firebase/auth');
|
|
28
|
+
return signInWithEmailAndPassword(this.auth, email, password);
|
|
29
|
+
}
|
|
30
|
+
}
|