@liorian/sdk 0.28.0-alpha.56010ca
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 +483 -0
- package/package.json +94 -0
- package/schemas/module.schema.json +818 -0
- package/src/application/index.ts +3 -0
- package/src/application/module-instance.ts +36 -0
- package/src/application/service/auth-api-service.ts +291 -0
- package/src/application/service/auth-user.service.ts +108 -0
- package/src/application/service/index.ts +12 -0
- package/src/application/service/module-activation-api.service.ts +105 -0
- package/src/application/service/notifications-api-service.ts +38 -0
- package/src/application/service/notifications-push.service.ts +299 -0
- package/src/application/service/notifications-socket-service.ts +76 -0
- package/src/application/service/organizations-api-service.ts +104 -0
- package/src/application/service/otp-api-service.ts +11 -0
- package/src/application/service/storage-api-service.ts +77 -0
- package/src/application/service/storefront-api.service.ts +72 -0
- package/src/application/service/user-preferences-api.service.ts +57 -0
- package/src/domain/config/app.config.ts +35 -0
- package/src/domain/config/auth.config.ts +30 -0
- package/src/domain/config/index.ts +4 -0
- package/src/domain/config/logo.config.ts +17 -0
- package/src/domain/entities/activities.interface.ts +18 -0
- package/src/domain/entities/catalog.interface.ts +96 -0
- package/src/domain/entities/index.ts +13 -0
- package/src/domain/entities/media.ts +22 -0
- package/src/domain/entities/module-activation.interface.ts +173 -0
- package/src/domain/entities/module.interface.ts +101 -0
- package/src/domain/entities/notification.interface.ts +23 -0
- package/src/domain/entities/objectable.type.ts +3 -0
- package/src/domain/entities/organization.interface.ts +81 -0
- package/src/domain/entities/user-auth.interface.ts +17 -0
- package/src/domain/entities/user-phone.interface.ts +11 -0
- package/src/domain/entities/user-profile.interface.ts +23 -0
- package/src/domain/entities/user.interface.ts +79 -0
- package/src/domain/enums/coloring.enum.ts +6 -0
- package/src/domain/enums/domains.enum.ts +282 -0
- package/src/domain/enums/index.ts +8 -0
- package/src/domain/enums/module-category.enum.ts +57 -0
- package/src/domain/enums/module.enum.ts +97 -0
- package/src/domain/enums/routine.enum.ts +12 -0
- package/src/domain/enums/user-profile.enum.ts +25 -0
- package/src/domain/enums/user-status.enum.ts +7 -0
- package/src/domain/index.ts +6 -0
- package/src/domain/typing/auth-caches.ts +12 -0
- package/src/domain/typing/auth-oauth.types.ts +172 -0
- package/src/domain/typing/devise.ts +2 -0
- package/src/domain/typing/index.ts +10 -0
- package/src/domain/typing/response.ts +30 -0
- package/src/domain/typing/routine.types.ts +113 -0
- package/src/domain/typing/session.interface.ts +28 -0
- package/src/domain/typing/statisticals.ts +11 -0
- package/src/domain/typing/user-media-upload.ts +9 -0
- package/src/domain/typing/values.ts +6 -0
- package/src/index.tsx +168 -0
- package/src/infrastructure/capabilities/cache-storage/capability.ts +129 -0
- package/src/infrastructure/capabilities/cache-storage/enum.ts +5 -0
- package/src/infrastructure/capabilities/cache-storage/index.ts +4 -0
- package/src/infrastructure/capabilities/cache-storage/types.ts +13 -0
- package/src/infrastructure/capabilities/index.ts +3 -0
- package/src/infrastructure/capabilities/stackable/capability.ts +55 -0
- package/src/infrastructure/capabilities/stackable/index.ts +3 -0
- package/src/infrastructure/capabilities/stackable/types.ts +24 -0
- package/src/infrastructure/common-classname.constant.ts +4 -0
- package/src/infrastructure/data/countries.ts +208 -0
- package/src/infrastructure/data/gender.ts +5 -0
- package/src/infrastructure/data/index.ts +3 -0
- package/src/infrastructure/dataset/index.ts +3 -0
- package/src/infrastructure/dataset/sign-in.dataset.ts +21 -0
- package/src/infrastructure/dataset/sign-up.dataset.ts +64 -0
- package/src/infrastructure/hooks/index.ts +10 -0
- package/src/infrastructure/hooks/use-activity-lock-draft.ts +69 -0
- package/src/infrastructure/hooks/use-auth.ts +24 -0
- package/src/infrastructure/hooks/use-elastic-interaction.ts +180 -0
- package/src/infrastructure/hooks/use-is-desktop.ts +25 -0
- package/src/infrastructure/hooks/use-is-tauri.ts +17 -0
- package/src/infrastructure/hooks/use-mobile.ts +19 -0
- package/src/infrastructure/hooks/use-notifications.ts +128 -0
- package/src/infrastructure/hooks/use-recent-colors.ts +113 -0
- package/src/infrastructure/hooks/use-recent-modules.ts +76 -0
- package/src/infrastructure/index.tsx +11 -0
- package/src/infrastructure/library/design-system.ts +95 -0
- package/src/infrastructure/library/index.tsx +5 -0
- package/src/infrastructure/library/motion-utils.ts +77 -0
- package/src/infrastructure/library/motion.tsx +31 -0
- package/src/infrastructure/library/scroll-progress.tsx +93 -0
- package/src/infrastructure/providers/auth-guard.provider.tsx +47 -0
- package/src/infrastructure/providers/auth.provider.tsx +168 -0
- package/src/infrastructure/providers/index.tsx +8 -0
- package/src/infrastructure/providers/module-usage.provider.tsx +95 -0
- package/src/infrastructure/providers/modules-guard.provider.tsx +64 -0
- package/src/infrastructure/providers/modules-routines.provider.tsx +33 -0
- package/src/infrastructure/providers/modules-switcher.provider.tsx +25 -0
- package/src/infrastructure/providers/query.provider.tsx +27 -0
- package/src/infrastructure/routines/__tests__/routine.test.ts +368 -0
- package/src/infrastructure/routines/index.tsx +3 -0
- package/src/infrastructure/routines/routine.hook.ts +121 -0
- package/src/infrastructure/routines/routine.ts +283 -0
- package/src/infrastructure/routines/routine.tsx +283 -0
- package/src/infrastructure/stores/activity-lock.store.ts +22 -0
- package/src/infrastructure/stores/auth-user-connected.store.ts +24 -0
- package/src/infrastructure/stores/dataset.store.ts +106 -0
- package/src/infrastructure/stores/index.ts +6 -0
- package/src/infrastructure/stores/module.store.ts +266 -0
- package/src/infrastructure/stores/upload.store.ts +198 -0
- package/src/infrastructure/utilities/access-label.util.ts +158 -0
- package/src/infrastructure/utilities/activities.util.tsx +54 -0
- package/src/infrastructure/utilities/activity-lock.service.ts +263 -0
- package/src/infrastructure/utilities/activity-tracker.service.ts +205 -0
- package/src/infrastructure/utilities/api-service.ts +237 -0
- package/src/infrastructure/utilities/auth-logout.util.ts +23 -0
- package/src/infrastructure/utilities/classname.util.ts +7 -0
- package/src/infrastructure/utilities/encryption.service.ts +125 -0
- package/src/infrastructure/utilities/form-snapshot.service.ts +304 -0
- package/src/infrastructure/utilities/format.util.ts +40 -0
- package/src/infrastructure/utilities/index.tsx +23 -0
- package/src/infrastructure/utilities/media-label.service.ts +47 -0
- package/src/infrastructure/utilities/modules.ts +11 -0
- package/src/infrastructure/utilities/permission.util.ts +22 -0
- package/src/infrastructure/utilities/push-permission.util.ts +43 -0
- package/src/infrastructure/utilities/route-access.ts +38 -0
- package/src/infrastructure/utilities/socket-service.ts +123 -0
- package/src/infrastructure/utilities/sso-auth.util.ts +188 -0
- package/src/infrastructure/utilities/stringable.util.ts +16 -0
- package/src/infrastructure/utilities/strings.util.ts +18 -0
- package/src/infrastructure/utilities/tauri.util.ts +15 -0
- package/src/infrastructure/utilities/utils.ts +6 -0
- package/src/infrastructure/utilities/values.ts +5 -0
- package/src/presentation/8starlabs-ui/button.tsx +94 -0
- package/src/presentation/8starlabs-ui/flip-clock.tsx +368 -0
- package/src/presentation/8starlabs-ui/heatmap.tsx +430 -0
- package/src/presentation/8starlabs-ui/hooks/index.tsx +2 -0
- package/src/presentation/8starlabs-ui/hooks/use-mobile.ts +21 -0
- package/src/presentation/8starlabs-ui/index.tsx +20 -0
- package/src/presentation/8starlabs-ui/json-viewer.tsx +972 -0
- package/src/presentation/8starlabs-ui/marquee.tsx +134 -0
- package/src/presentation/8starlabs-ui/partition-bar.tsx +198 -0
- package/src/presentation/8starlabs-ui/scroll-fade.tsx +158 -0
- package/src/presentation/8starlabs-ui/status-indicator.tsx +86 -0
- package/src/presentation/8starlabs-ui/system-banner.tsx +50 -0
- package/src/presentation/8starlabs-ui/timeline.tsx +576 -0
- package/src/presentation/8starlabs-ui/transport-badge.tsx +249 -0
- package/src/presentation/8starlabs-ui/ui/copy-button.tsx +60 -0
- package/src/presentation/8starlabs-ui/ui/index.tsx +2 -0
- package/src/presentation/analytics-section.tsx +85 -0
- package/src/presentation/app-sidebar.tsx +234 -0
- package/src/presentation/charts/area-widget.chart-skeleton.tsx +61 -0
- package/src/presentation/charts/area-widget.chart.tsx +128 -0
- package/src/presentation/charts/chart-widgets.types.ts +81 -0
- package/src/presentation/charts/index.tsx +16 -0
- package/src/presentation/charts/line-widget.chart-skeleton.tsx +64 -0
- package/src/presentation/charts/line-widget.chart.tsx +105 -0
- package/src/presentation/charts/pie-widget.chart-skeleton.tsx +56 -0
- package/src/presentation/charts/pie-widget.chart.tsx +168 -0
- package/src/presentation/charts/radar-widget.chart-skeleton.tsx +67 -0
- package/src/presentation/charts/radar-widget.chart.tsx +80 -0
- package/src/presentation/charts/radial-stacked-widget.chart-skeleton.tsx +66 -0
- package/src/presentation/charts/radial-stacked-widget.chart.tsx +120 -0
- package/src/presentation/charts/radial-widget.chart-skeleton.tsx +61 -0
- package/src/presentation/charts/radial-widget.chart.tsx +87 -0
- package/src/presentation/charts/tooltip-widget.chart-skeleton.tsx +64 -0
- package/src/presentation/charts/tooltip-widget.chart.tsx +89 -0
- package/src/presentation/components/activity-actions.tsx +20 -0
- package/src/presentation/components/activity-container.tsx +55 -0
- package/src/presentation/components/activity-content.tsx +20 -0
- package/src/presentation/components/activity-descriptor-parser.tsx +37 -0
- package/src/presentation/components/activity-header.tsx +20 -0
- package/src/presentation/components/activity-loader.tsx +19 -0
- package/src/presentation/components/activity-title.tsx +32 -0
- package/src/presentation/components/activity.tsx +22 -0
- package/src/presentation/components/auth/index.tsx +4 -0
- package/src/presentation/components/auth/sso-callback.view.tsx +44 -0
- package/src/presentation/components/auth/sso-disconnect.view.tsx +41 -0
- package/src/presentation/components/auth/sso-sign-in.view.tsx +29 -0
- package/src/presentation/components/auth-lock-screen.tsx +309 -0
- package/src/presentation/components/auth-session.view.tsx +17 -0
- package/src/presentation/components/data-table.tsx +814 -0
- package/src/presentation/components/dynamic-icon.tsx +16 -0
- package/src/presentation/components/floating-upload.tsx +291 -0
- package/src/presentation/components/index.tsx +40 -0
- package/src/presentation/components/local-time.tsx +170 -0
- package/src/presentation/components/markdown-parser.tsx +201 -0
- package/src/presentation/components/message-scroller.tsx +131 -0
- package/src/presentation/components/module-first-access-config.view.tsx +181 -0
- package/src/presentation/components/module-first-access-pending.view.tsx +38 -0
- package/src/presentation/components/module-guard.tsx +34 -0
- package/src/presentation/components/modules-first-access.guard.tsx +88 -0
- package/src/presentation/components/modules-guard-forbidden.tsx +46 -0
- package/src/presentation/components/notification-details.modal.tsx +145 -0
- package/src/presentation/components/notification-item.tsx +127 -0
- package/src/presentation/components/notifications-popover.tsx +128 -0
- package/src/presentation/components/push-notifications.provider.tsx +65 -0
- package/src/presentation/components/upload-progress-dialog.tsx +140 -0
- package/src/presentation/components/waiting-bar.tsx +22 -0
- package/src/presentation/components/waiting-section.tsx +15 -0
- package/src/presentation/components/waiting.tsx +14 -0
- package/src/presentation/data-grid/data-grid-empty.tsx +24 -0
- package/src/presentation/data-grid/data-grid-search-engine.tsx +32 -0
- package/src/presentation/data-grid/data-grid.tsx +892 -0
- package/src/presentation/data-grid/index.tsx +4 -0
- package/src/presentation/form-screen.tsx +95 -0
- package/src/presentation/icons/custom.tsx +20 -0
- package/src/presentation/icons/glyphIcons.tsx +53 -0
- package/src/presentation/icons/icons.enum.ts +4 -0
- package/src/presentation/icons/icons.util.ts +6 -0
- package/src/presentation/icons/index.tsx +8 -0
- package/src/presentation/icons/lucide.tsx +17 -0
- package/src/presentation/icons/remix.tsx +33 -0
- package/src/presentation/icons/types.ts +27 -0
- package/src/presentation/index.tsx +45 -0
- package/src/presentation/modals/components/ModalBox.tsx +74 -0
- package/src/presentation/modals/components/ModalPortal.tsx +31 -0
- package/src/presentation/modals/components/ModalStepper.tsx +319 -0
- package/src/presentation/modals/components/ModalWrapper.tsx +65 -0
- package/src/presentation/modals/components/index.tsx +5 -0
- package/src/presentation/modals/hooks/index.ts +2 -0
- package/src/presentation/modals/hooks/useModal.ts +38 -0
- package/src/presentation/modals/index.tsx +5 -0
- package/src/presentation/modals/stores/index.ts +2 -0
- package/src/presentation/modals/stores/useModalStore.ts +90 -0
- package/src/presentation/modals/types/index.ts +2 -0
- package/src/presentation/modals/types/modal.type.ts +33 -0
- package/src/presentation/module-menubar.tsx +300 -0
- package/src/presentation/module-widget.tsx +233 -0
- package/src/presentation/nav-documents.tsx +89 -0
- package/src/presentation/nav-main.tsx +62 -0
- package/src/presentation/nav-secondary.tsx +41 -0
- package/src/presentation/nav-user.tsx +109 -0
- package/src/presentation/permission-action-badge.tsx +35 -0
- package/src/presentation/reui/index.tsx +3 -0
- package/src/presentation/reui/stepper.tsx +508 -0
- package/src/presentation/reui/timeline.tsx +226 -0
- package/src/presentation/sheets/index.tsx +2 -0
- package/src/presentation/sheets/legacy-sheet-header.tsx +0 -0
- package/src/presentation/sheets/legacy-sheet.tsx +37 -0
- package/src/presentation/size.enum.ts +19 -0
- package/src/presentation/system/index.tsx +3 -0
- package/src/presentation/system/logo.theme.tsx +21 -0
- package/src/presentation/system/types.ts +10 -0
- package/src/presentation/theme/index.tsx +6 -0
- package/src/presentation/theme/prefer-color-scheme.tsx +50 -0
- package/src/presentation/theme/switch-color-scheme.tsx +16 -0
- package/src/presentation/theme/theme-switcher-button.tsx +20 -0
- package/src/presentation/theme/theme.enum.ts +4 -0
- package/src/presentation/theme/theme.store.ts +87 -0
- package/src/presentation/themes/index.tsx +2 -0
- package/src/presentation/themes/katon/dashboard-layout.tsx +1080 -0
- package/src/presentation/themes/katon/edge-section.tsx +19 -0
- package/src/presentation/themes/katon/header-menubar.tsx +270 -0
- package/src/presentation/themes/katon/header-modules-available.tsx +44 -0
- package/src/presentation/themes/katon/header-modules-recents.tsx +52 -0
- package/src/presentation/themes/katon/header-modules-store.tsx +25 -0
- package/src/presentation/themes/katon/header-options.tsx +13 -0
- package/src/presentation/themes/katon/header-organization-select.tsx +146 -0
- package/src/presentation/themes/katon/header-routines.tsx +74 -0
- package/src/presentation/themes/katon/header-tasks-connected-user.tsx +150 -0
- package/src/presentation/themes/katon/index.tsx +16 -0
- package/src/presentation/themes/katon/main-wrapper.tsx +34 -0
- package/src/presentation/themes/katon/module-item-link.tsx +32 -0
- package/src/presentation/themes/katon/view-frame.tsx +25 -0
- package/src/presentation/themes/katon/view-helmet.tsx +53 -0
- package/src/presentation/themes/katon/view-status.tsx +32 -0
- package/src/presentation/themes/katon/view-wrapper.tsx +15 -0
- package/src/presentation/themes/katon/view.tsx +42 -0
- package/src/presentation/ui/accordion-multiselect.tsx +206 -0
- package/src/presentation/ui/accordion.tsx +81 -0
- package/src/presentation/ui/alert-dialog.tsx +199 -0
- package/src/presentation/ui/alert.tsx +76 -0
- package/src/presentation/ui/aspect-ratio.tsx +11 -0
- package/src/presentation/ui/attachment.tsx +204 -0
- package/src/presentation/ui/avatar.tsx +112 -0
- package/src/presentation/ui/badge.tsx +49 -0
- package/src/presentation/ui/breadcrumb.tsx +122 -0
- package/src/presentation/ui/bubble.tsx +125 -0
- package/src/presentation/ui/button-group.tsx +83 -0
- package/src/presentation/ui/button.tsx +87 -0
- package/src/presentation/ui/calendar.tsx +222 -0
- package/src/presentation/ui/card.tsx +106 -0
- package/src/presentation/ui/carousel.tsx +242 -0
- package/src/presentation/ui/chart.tsx +373 -0
- package/src/presentation/ui/checkbox.tsx +33 -0
- package/src/presentation/ui/clickable.tsx +58 -0
- package/src/presentation/ui/collapsible.tsx +33 -0
- package/src/presentation/ui/color-swatch-selector.tsx +81 -0
- package/src/presentation/ui/combobox.tsx +299 -0
- package/src/presentation/ui/command.tsx +195 -0
- package/src/presentation/ui/context-menu.tsx +263 -0
- package/src/presentation/ui/dialog.tsx +168 -0
- package/src/presentation/ui/direction.tsx +22 -0
- package/src/presentation/ui/drawer.tsx +134 -0
- package/src/presentation/ui/dropdown-menu.tsx +269 -0
- package/src/presentation/ui/empty.tsx +104 -0
- package/src/presentation/ui/field.tsx +238 -0
- package/src/presentation/ui/granularity-selector.tsx +58 -0
- package/src/presentation/ui/hover-card.tsx +44 -0
- package/src/presentation/ui/index.tsx +74 -0
- package/src/presentation/ui/input-group.tsx +156 -0
- package/src/presentation/ui/input-otp.tsx +87 -0
- package/src/presentation/ui/input.tsx +19 -0
- package/src/presentation/ui/item.tsx +196 -0
- package/src/presentation/ui/kpi-card.tsx +203 -0
- package/src/presentation/ui/label-selector.tsx +100 -0
- package/src/presentation/ui/label.tsx +24 -0
- package/src/presentation/ui/legacy-birth-date-input.tsx +43 -0
- package/src/presentation/ui/legacy-color-picker/index.tsx +42 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.content.tsx +37 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.context.tsx +42 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.hex-input.tsx +97 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.picker.tsx +266 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.presets.ts +22 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.quick-list.tsx +49 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.recent.tsx +43 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.swatches.tsx +111 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.trigger.tsx +128 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.tsx +110 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.types.ts +1 -0
- package/src/presentation/ui/legacy-color-picker/legacy-color-picker.utils.ts +114 -0
- package/src/presentation/ui/legacy-country-input.tsx +171 -0
- package/src/presentation/ui/legacy-gender-input.tsx +22 -0
- package/src/presentation/ui/legacy-input.tsx +106 -0
- package/src/presentation/ui/legacy-phone-input.tsx +213 -0
- package/src/presentation/ui/legacy-select-input.tsx +114 -0
- package/src/presentation/ui/legacy-text-area.tsx +111 -0
- package/src/presentation/ui/list-items.tsx +59 -0
- package/src/presentation/ui/marker.tsx +69 -0
- package/src/presentation/ui/media-player/index.tsx +12 -0
- package/src/presentation/ui/media-player/media-player.context.tsx +36 -0
- package/src/presentation/ui/media-player/media-player.tsx +167 -0
- package/src/presentation/ui/media-player/player-controls.tsx +207 -0
- package/src/presentation/ui/media-player/players/audio-player.tsx +78 -0
- package/src/presentation/ui/media-player/players/index.tsx +4 -0
- package/src/presentation/ui/media-player/players/video-player.tsx +193 -0
- package/src/presentation/ui/media-player/players/voice-note-player.tsx +108 -0
- package/src/presentation/ui/media-player/use-media-player.hook.ts +263 -0
- package/src/presentation/ui/menubar.tsx +281 -0
- package/src/presentation/ui/message.tsx +92 -0
- package/src/presentation/ui/navigation-menu.tsx +172 -0
- package/src/presentation/ui/pagination.tsx +129 -0
- package/src/presentation/ui/popover.tsx +89 -0
- package/src/presentation/ui/progress.tsx +31 -0
- package/src/presentation/ui/resizable.tsx +50 -0
- package/src/presentation/ui/select.tsx +192 -0
- package/src/presentation/ui/separator.tsx +28 -0
- package/src/presentation/ui/sheet.tsx +147 -0
- package/src/presentation/ui/sidebar.tsx +702 -0
- package/src/presentation/ui/skeleton.tsx +13 -0
- package/src/presentation/ui/slide-to-confirm.tsx +129 -0
- package/src/presentation/ui/slider.tsx +59 -0
- package/src/presentation/ui/sonner.tsx +58 -0
- package/src/presentation/ui/switch.tsx +33 -0
- package/src/presentation/ui/table.tsx +116 -0
- package/src/presentation/ui/tabs.tsx +90 -0
- package/src/presentation/ui/text-gradient.tsx +84 -0
- package/src/presentation/ui/textarea.tsx +18 -0
- package/src/presentation/ui/timeline-steps.tsx +235 -0
- package/src/presentation/ui/toggle-group.tsx +89 -0
- package/src/presentation/ui/toggle.tsx +47 -0
- package/src/presentation/ui/tooltip.tsx +57 -0
- package/src/presentation/uploading/index.tsx +3 -0
- package/src/presentation/uploading/media-upload-field.tsx +152 -0
- package/src/presentation/uploading/storage-media.tsx +210 -0
- package/src/styles.css +403 -0
- package/src/themes.css +1038 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/** Évènement window émis juste avant le verrouillage (le DOM des formulaires est encore monté). */
|
|
4
|
+
export const ACTIVITY_LOCK_EVENT = "liorian:activity-lock";
|
|
5
|
+
|
|
6
|
+
/** Évènement window émis juste après le déverrouillage (les formulaires sont remontés ensuite). */
|
|
7
|
+
export const ACTIVITY_UNLOCK_EVENT = "liorian:activity-unlock";
|
|
8
|
+
|
|
9
|
+
/** Clé sessionStorage du brouillon de formulaire : survit au rafraîchissement pendant le verrou. */
|
|
10
|
+
export const ACTIVITY_LOCK_FORM_DRAFT_KEY = "@liorian-activity-lock-form-draft";
|
|
11
|
+
|
|
12
|
+
export interface ActivityLockFormField {
|
|
13
|
+
/** Sélecteur/chemin unique permettant de retrouver le champ à la restauration. */
|
|
14
|
+
key: string;
|
|
15
|
+
tagName: "INPUT" | "SELECT" | "TEXTAREA";
|
|
16
|
+
/** Type de l'élément (utile pour les checkbox/radio). */
|
|
17
|
+
type?: string;
|
|
18
|
+
value?: string;
|
|
19
|
+
/** Valeurs sélectionnées pour les `<select multiple>`. */
|
|
20
|
+
values?: string[];
|
|
21
|
+
checked?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ActivityLockFormSnapshot {
|
|
25
|
+
capturedAt: number;
|
|
26
|
+
fields: ActivityLockFormField[];
|
|
27
|
+
/** Clé du champ qui avait le focus au moment du verrouillage. */
|
|
28
|
+
focusKey?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Sauvegarde et restauration automatique de l'état des formulaires lors d'un
|
|
33
|
+
* verrouillage d'activité.
|
|
34
|
+
*
|
|
35
|
+
* - `FormSnapshotService.bind()` écoute les évènements de verrouillage/déverrouillage
|
|
36
|
+
* émis par `ActivityLockService` et, sans configuration, sauvegarde tous les champs
|
|
37
|
+
* montés (`input`, `select`, `textarea` - hors `password`, `file`, `hidden` et champs
|
|
38
|
+
* désactivés) avant l'affichage de l'écran de verrouillage, puis les restaure à la
|
|
39
|
+
* réouverture de l'interface (valeur + focus du champ précédemment édité).
|
|
40
|
+
* - Les champs sont retrouvés par clé stable (id → name scoped au formulaire → attribut
|
|
41
|
+
* descriptif → chemin structurel) ; la restauration utilise le setter natif + un
|
|
42
|
+
* évènement `input`/`change` pour être compatible avec les champs contrôlés React.
|
|
43
|
+
* - Le brouillon est stocké dans le `sessionStorage` : il survit au rafraîchissement
|
|
44
|
+
* de la page pendant que l'écran reste verrouillé.
|
|
45
|
+
*/
|
|
46
|
+
export class FormSnapshotService {
|
|
47
|
+
|
|
48
|
+
protected static bound = false;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* (R)attache les écouteurs de verrouillage/déverrouillage (idempotent).
|
|
52
|
+
* Appelé automatiquement par `ActivityLockService.start()`.
|
|
53
|
+
*/
|
|
54
|
+
static bind(): void {
|
|
55
|
+
if (typeof window === "undefined" || this.bound) return;
|
|
56
|
+
this.bound = true;
|
|
57
|
+
|
|
58
|
+
const onLock = () => {
|
|
59
|
+
this.save();
|
|
60
|
+
};
|
|
61
|
+
const onUnlock = () => {
|
|
62
|
+
this.restoreAfterUnlock();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
window.addEventListener(ACTIVITY_LOCK_EVENT, onLock);
|
|
66
|
+
window.addEventListener(ACTIVITY_UNLOCK_EVENT, onUnlock);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Sauvegarde l'état courant des formulaires montés dans le `sessionStorage`. */
|
|
70
|
+
static save(): boolean {
|
|
71
|
+
if (typeof document === "undefined" || typeof window === "undefined") return false;
|
|
72
|
+
|
|
73
|
+
const fields = this.collectControls();
|
|
74
|
+
const snapshot: ActivityLockFormSnapshot = {
|
|
75
|
+
capturedAt: Date.now(),
|
|
76
|
+
fields,
|
|
77
|
+
focusKey: this.elementKey(document.activeElement),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
window.sessionStorage.setItem(ACTIVITY_LOCK_FORM_DRAFT_KEY, JSON.stringify(snapshot));
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Restaure une seule fois le brouillon (si présent) puis l'efface. */
|
|
89
|
+
static restore(): boolean {
|
|
90
|
+
if (typeof document === "undefined" || typeof window === "undefined") return false;
|
|
91
|
+
|
|
92
|
+
const snapshot = this.read();
|
|
93
|
+
if (!snapshot) return false;
|
|
94
|
+
|
|
95
|
+
for (const field of snapshot.fields) {
|
|
96
|
+
const element = this.findByKey(field.key);
|
|
97
|
+
if (element) this.applyField(element, field);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (snapshot.focusKey) {
|
|
101
|
+
const element = this.findByKey(snapshot.focusKey);
|
|
102
|
+
if (element instanceof HTMLElement) {
|
|
103
|
+
element.focus({preventScroll: true});
|
|
104
|
+
element.scrollIntoView({block: "center", behavior: "auto"});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
window.sessionStorage.removeItem(ACTIVITY_LOCK_FORM_DRAFT_KEY);
|
|
110
|
+
} catch {
|
|
111
|
+
// Stockage indisponible : on ignore.
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Restaure le brouillon après le déverrouillage. Les champs cibles sont remontés
|
|
118
|
+
* de façon asynchrone par React, on tente donc de les retrouver pendant un court
|
|
119
|
+
* laps de temps avant d'appliquer le brouillon.
|
|
120
|
+
*/
|
|
121
|
+
static restoreAfterUnlock(maxAttempts = 50, intervalMs = 40): void {
|
|
122
|
+
if (typeof window === "undefined") return;
|
|
123
|
+
const snapshot = this.read();
|
|
124
|
+
if (!snapshot || snapshot.fields.length === 0) return;
|
|
125
|
+
|
|
126
|
+
let attempts = 0;
|
|
127
|
+
const attempt = (): void => {
|
|
128
|
+
attempts += 1;
|
|
129
|
+
const ready = snapshot.fields.every((field) => this.findByKey(field.key));
|
|
130
|
+
if (ready || attempts >= maxAttempts) {
|
|
131
|
+
this.restore();
|
|
132
|
+
} else {
|
|
133
|
+
window.setTimeout(attempt, intervalMs);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
window.setTimeout(attempt, intervalMs);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected static collectControls(): ActivityLockFormField[] {
|
|
140
|
+
const fields: ActivityLockFormField[] = [];
|
|
141
|
+
const nodes = document.querySelectorAll<HTMLElement>("input, select, textarea");
|
|
142
|
+
|
|
143
|
+
nodes.forEach((el) => {
|
|
144
|
+
if (el instanceof HTMLInputElement) {
|
|
145
|
+
if (el.disabled || el.readOnly || el.type === "hidden" || el.type === "password" || el.type === "file") {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
} else if (el instanceof HTMLSelectElement) {
|
|
149
|
+
if (el.disabled) return;
|
|
150
|
+
} else if (el instanceof HTMLTextAreaElement) {
|
|
151
|
+
if (el.disabled || el.readOnly) return;
|
|
152
|
+
}
|
|
153
|
+
const key = this.elementKey(el);
|
|
154
|
+
if (!key) return;
|
|
155
|
+
|
|
156
|
+
if (el instanceof HTMLInputElement) {
|
|
157
|
+
fields.push({key, tagName: "INPUT", type: el.type, value: el.value, checked: el.checked});
|
|
158
|
+
} else if (el instanceof HTMLSelectElement) {
|
|
159
|
+
fields.push({
|
|
160
|
+
key,
|
|
161
|
+
tagName: "SELECT",
|
|
162
|
+
value: el.value,
|
|
163
|
+
values: el.multiple ? Array.from(el.selectedOptions).map((option) => option.value) : undefined,
|
|
164
|
+
});
|
|
165
|
+
} else if (el instanceof HTMLTextAreaElement) {
|
|
166
|
+
fields.push({key, tagName: "TEXTAREA", value: el.value});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return fields;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected static read(): ActivityLockFormSnapshot | undefined {
|
|
174
|
+
try {
|
|
175
|
+
const raw = window.sessionStorage.getItem(ACTIVITY_LOCK_FORM_DRAFT_KEY);
|
|
176
|
+
if (!raw) return undefined;
|
|
177
|
+
const snapshot = JSON.parse(raw) as ActivityLockFormSnapshot;
|
|
178
|
+
return Array.isArray(snapshot?.fields) ? snapshot : undefined;
|
|
179
|
+
} catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Construit une clé stable (id → name→ attribut descriptif → chemin structurel). */
|
|
185
|
+
protected static elementKey(element: Element | null): string | undefined {
|
|
186
|
+
if (!element) return undefined;
|
|
187
|
+
if (!(element instanceof HTMLInputElement)
|
|
188
|
+
&& !(element instanceof HTMLSelectElement)
|
|
189
|
+
&& !(element instanceof HTMLTextAreaElement)) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const tag = element.tagName.toLowerCase();
|
|
194
|
+
const form = element.closest("form");
|
|
195
|
+
const formRef = form && form instanceof HTMLFormElement
|
|
196
|
+
? form.id
|
|
197
|
+
? `form[id="${this.escape(form.id)}"]`
|
|
198
|
+
: form.name
|
|
199
|
+
? `form[name="${this.escape(form.name)}"]`
|
|
200
|
+
: undefined
|
|
201
|
+
: undefined;
|
|
202
|
+
|
|
203
|
+
if (element.id) {
|
|
204
|
+
return `${tag}[id="${this.escape(element.id)}"]`;
|
|
205
|
+
}
|
|
206
|
+
if (element.name) {
|
|
207
|
+
const fieldRef = `${tag}[name="${this.escape(element.name)}"]`;
|
|
208
|
+
return formRef ? `${formRef} > ${fieldRef}` : fieldRef;
|
|
209
|
+
}
|
|
210
|
+
for (const attr of ["aria-label", "data-testid", "placeholder"] as const) {
|
|
211
|
+
const value = element.getAttribute(attr);
|
|
212
|
+
if (value) {
|
|
213
|
+
const fieldRef = `${tag}[${attr}="${this.escape(value)}"]`;
|
|
214
|
+
return formRef ? `${formRef} > ${fieldRef}` : fieldRef;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return this.structuralKey(element);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Chemin structurel (nth-child) lorsqu'aucun attribut fiable n'est disponible. */
|
|
221
|
+
protected static structuralKey(element: Element): string | undefined {
|
|
222
|
+
const segments: string[] = [];
|
|
223
|
+
let current: Element | null = element;
|
|
224
|
+
let root: Element | null = element.closest("form");
|
|
225
|
+
|
|
226
|
+
while (current && current !== document.body) {
|
|
227
|
+
const host: HTMLElement | null = current.parentElement;
|
|
228
|
+
if (!host) return undefined;
|
|
229
|
+
const index = Array.from(host.children).indexOf(current);
|
|
230
|
+
if (index < 0) return undefined;
|
|
231
|
+
segments.unshift(`${current.tagName.toLowerCase()}:nth-child(${index + 1})`);
|
|
232
|
+
if (host === root || host === document.body) break;
|
|
233
|
+
current = host;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (root && root !== document.body && root instanceof HTMLFormElement) {
|
|
237
|
+
const rootRef = root.id
|
|
238
|
+
? `form[id="${this.escape(root.id)}"]`
|
|
239
|
+
: root.name
|
|
240
|
+
? `form[name="${this.escape(root.name)}"]`
|
|
241
|
+
: "form";
|
|
242
|
+
return `${rootRef} > ${segments.join(" > ")}`;
|
|
243
|
+
}
|
|
244
|
+
return `body > ${segments.join(" > ")}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
protected static findByKey(key: string): Element | null {
|
|
248
|
+
if (typeof document === "undefined") return null;
|
|
249
|
+
try {
|
|
250
|
+
return document.querySelector(key);
|
|
251
|
+
} catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Applique la valeur sauvegardée de façon compatible avec les champs contrôlés React. */
|
|
257
|
+
protected static applyField(element: Element, field: ActivityLockFormField): void {
|
|
258
|
+
if (element instanceof HTMLInputElement) {
|
|
259
|
+
if (field.type === "checkbox" || field.type === "radio") {
|
|
260
|
+
if (element.checked !== !!field.checked) element.click();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.setNativeValue(element, field.value ?? "");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (element instanceof HTMLTextAreaElement) {
|
|
267
|
+
this.setNativeValue(element, field.value ?? "");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (element instanceof HTMLSelectElement) {
|
|
271
|
+
if (element.multiple && field.values) {
|
|
272
|
+
Array.from(element.options).forEach((option) => {
|
|
273
|
+
option.selected = field.values?.includes(option.value) ?? false;
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
element.value = field.value ?? "";
|
|
277
|
+
}
|
|
278
|
+
element.dispatchEvent(new Event("input", {bubbles: true}));
|
|
279
|
+
element.dispatchEvent(new Event("change", {bubbles: true}));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Set le contrôle via le setter natif puis déclenche `input`/`change` (gère React). */
|
|
284
|
+
protected static setNativeValue(
|
|
285
|
+
element: HTMLInputElement | HTMLTextAreaElement,
|
|
286
|
+
value: string
|
|
287
|
+
): void {
|
|
288
|
+
const prototype = element instanceof HTMLTextAreaElement
|
|
289
|
+
? HTMLTextAreaElement.prototype
|
|
290
|
+
: HTMLInputElement.prototype;
|
|
291
|
+
const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
|
|
292
|
+
if (setter) {
|
|
293
|
+
setter.call(element, value);
|
|
294
|
+
} else {
|
|
295
|
+
element.value = value;
|
|
296
|
+
}
|
|
297
|
+
element.dispatchEvent(new Event("input", {bubbles: true}));
|
|
298
|
+
element.dispatchEvent(new Event("change", {bubbles: true}));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
protected static escape(value: string): string {
|
|
302
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function formatAmount(value: number, currency: string = 'XOF', locale: string = 'fr-FR'): string {
|
|
2
|
+
return `${Math.round(value).toLocaleString(locale)} ${currency}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function formatFileSize(bytes: number): string {
|
|
6
|
+
if (isNaN(bytes) || bytes === null || typeof bytes === 'undefined') return '0 o';
|
|
7
|
+
const units = ['o', 'Ko', 'Mo', 'Go', 'To'];
|
|
8
|
+
let i = 0;
|
|
9
|
+
let value = bytes;
|
|
10
|
+
while (value >= 1024 && i < units.length - 1) {
|
|
11
|
+
value /= 1024;
|
|
12
|
+
i++;
|
|
13
|
+
}
|
|
14
|
+
const decimals = i === 0 ? 0 : value >= 100 ? 0 : value >= 10 ? 1 : 2;
|
|
15
|
+
return `${value.toFixed(decimals)} ${units[i]}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatBytesPerSecond(bytesPerSecond: number): string {
|
|
19
|
+
return `${formatFileSize(bytesPerSecond)}/s`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatEta(seconds: number | null | undefined): string {
|
|
23
|
+
if (!seconds || isNaN(seconds) || seconds <= 0 || !isFinite(seconds)) return '—';
|
|
24
|
+
if (seconds < 60) return `${Math.ceil(seconds)}s restantes`;
|
|
25
|
+
const minutes = Math.floor(seconds / 60);
|
|
26
|
+
const secs = Math.floor(seconds % 60);
|
|
27
|
+
if (minutes < 60) return `${minutes}m ${secs}s restantes`;
|
|
28
|
+
const hours = Math.floor(minutes / 60);
|
|
29
|
+
return `${hours}h ${minutes % 60}m restantes`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatDuration(seconds: number | null | undefined): string {
|
|
33
|
+
if (!seconds || isNaN(seconds) || seconds <= 0 || !isFinite(seconds)) return '—';
|
|
34
|
+
if (seconds < 60) return `${Math.ceil(seconds)}s`;
|
|
35
|
+
const minutes = Math.floor(seconds / 60);
|
|
36
|
+
const secs = Math.floor(seconds % 60);
|
|
37
|
+
if (minutes < 60) return `${minutes}m ${secs.toString().padStart(2, '0')}s`;
|
|
38
|
+
const hours = Math.floor(minutes / 60);
|
|
39
|
+
return `${hours}h ${(minutes % 60).toString().padStart(2, '0')}m`;
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @liorian/sdk/infrastructure/utilities — shared utilities & services.
|
|
2
|
+
export * from "./access-label.util";
|
|
3
|
+
export * from "./activities.util";
|
|
4
|
+
export * from "./activity-lock.service";
|
|
5
|
+
export * from "./activity-tracker.service";
|
|
6
|
+
export * from "./api-service";
|
|
7
|
+
export * from "./auth-logout.util";
|
|
8
|
+
export * from "./classname.util";
|
|
9
|
+
export * from "./encryption.service";
|
|
10
|
+
export * from "./form-snapshot.service";
|
|
11
|
+
export * from "./format.util";
|
|
12
|
+
export * from "./media-label.service";
|
|
13
|
+
export * from "./modules";
|
|
14
|
+
export * from "./permission.util";
|
|
15
|
+
export * from "./push-permission.util";
|
|
16
|
+
export * from "./route-access";
|
|
17
|
+
export * from "./socket-service";
|
|
18
|
+
export * from "./sso-auth.util";
|
|
19
|
+
export * from "./stringable.util";
|
|
20
|
+
export * from "./strings.util";
|
|
21
|
+
export * from "./tauri.util";
|
|
22
|
+
export * from "./utils";
|
|
23
|
+
export * from "./values";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {MediaSection, MediaUploadOptions} from "../../domain/entities/media";
|
|
2
|
+
|
|
3
|
+
type SectionSource = string | { mime?: string; extension?: string };
|
|
4
|
+
|
|
5
|
+
const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "avif"];
|
|
6
|
+
const VIDEO_EXTENSIONS = ["mp4", "webm", "mov", "avi", "mkv", "m4v"];
|
|
7
|
+
const AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "flac", "aac", "m4a"];
|
|
8
|
+
const DOCUMENT_EXTENSIONS = ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "csv", "rtf", "odt"];
|
|
9
|
+
|
|
10
|
+
export class MediaLabelService {
|
|
11
|
+
static sectionFor(source: SectionSource): MediaSection {
|
|
12
|
+
const mime = typeof source === "string" ? source.toLowerCase() : source?.mime?.toLowerCase();
|
|
13
|
+
const extension = typeof source === "string"
|
|
14
|
+
? ""
|
|
15
|
+
: source?.extension?.toLowerCase().replace(/^\./, "") ?? "";
|
|
16
|
+
|
|
17
|
+
if (mime) {
|
|
18
|
+
if (mime.startsWith("image/")) return "image";
|
|
19
|
+
if (mime.startsWith("video/")) return "video";
|
|
20
|
+
if (mime.startsWith("audio/")) return "audio";
|
|
21
|
+
if (mime === "application/pdf" || mime.startsWith("text/") || mime.includes("document")) return "document";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (extension) {
|
|
25
|
+
if (IMAGE_EXTENSIONS.includes(extension)) return "image";
|
|
26
|
+
if (VIDEO_EXTENSIONS.includes(extension)) return "video";
|
|
27
|
+
if (AUDIO_EXTENSIONS.includes(extension)) return "audio";
|
|
28
|
+
if (DOCUMENT_EXTENSIONS.includes(extension)) return "document";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return "autre";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static build(module: string, section: MediaSection, type?: string): string {
|
|
35
|
+
const label = `${module.toLowerCase()}:${section.toLowerCase()}`;
|
|
36
|
+
return type ? `${label}.${type.toLowerCase()}` : label;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static resolve(module: string, options: MediaUploadOptions, type?: string): string {
|
|
40
|
+
const section = options.section
|
|
41
|
+
? options.section.toLowerCase()
|
|
42
|
+
: options.isDocument
|
|
43
|
+
? "document"
|
|
44
|
+
: this.sectionFor(options.type ?? options.module ?? "");
|
|
45
|
+
return this.build(module, section as MediaSection, type);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {useModuleStore} from "../stores/module.store";
|
|
2
|
+
import {ModuleDeclarationInterface} from "../../domain/entities/module.interface";
|
|
3
|
+
|
|
4
|
+
export function retrieveModule(module?: string): ModuleDeclarationInterface | undefined {
|
|
5
|
+
return useModuleStore.getState().modules
|
|
6
|
+
.filter((m: ModuleDeclarationInterface) => {
|
|
7
|
+
const normalized = module?.replace(/-/g, '_').toLowerCase();
|
|
8
|
+
return m.key?.toLowerCase() === normalized
|
|
9
|
+
|| m.identifier?.toLowerCase() === module?.toLowerCase();
|
|
10
|
+
})[0] || undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {type UserInterface, type UserPermissionCapabilities} from "../../domain/entities/user.interface";
|
|
2
|
+
import {DomainsEnum} from "../../domain/enums/domains.enum";
|
|
3
|
+
|
|
4
|
+
export function hasPermissions(capabilities: UserPermissionCapabilities, domains: DomainsEnum | DomainsEnum[], strict: boolean = true): boolean {
|
|
5
|
+
const entries = Object.keys(capabilities)
|
|
6
|
+
return (Array.isArray(domains) ? domains : [domains])
|
|
7
|
+
[strict ? 'every' : 'some'](domain => entries.includes(domain))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Niveau hiérarchique minimal pour administrer une organisation (Admin = 80) */
|
|
11
|
+
export const ADMIN_ROLE_LEVEL = 80;
|
|
12
|
+
|
|
13
|
+
const ADMIN_ROLE_NAMES = ['root', 'superadmin', 'admin'];
|
|
14
|
+
|
|
15
|
+
export function isAdminOrAbove(user?: UserInterface | null, organizationId?: string | null): boolean {
|
|
16
|
+
return (user?.roles ?? []).some(role => {
|
|
17
|
+
if (organizationId && role.organizationId && role.organizationId !== organizationId) return false
|
|
18
|
+
const name = role.name?.toLowerCase().replace(/[\s_-]/g, '')
|
|
19
|
+
if (name && ADMIN_ROLE_NAMES.includes(name)) return true
|
|
20
|
+
return typeof role.level === 'number' && role.level >= ADMIN_ROLE_LEVEL
|
|
21
|
+
})
|
|
22
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {isTauri} from "./tauri.util";
|
|
2
|
+
|
|
3
|
+
export type PushPermissionState = "granted" | "denied" | "unsupported";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Demande l'autorisation d'afficher des notifications push.
|
|
7
|
+
* - Dans une application Tauri : via tauri-plugin-notification
|
|
8
|
+
* - Dans le navigateur : via l'API Notification standard
|
|
9
|
+
*/
|
|
10
|
+
export async function requestPushPermission(): Promise<PushPermissionState> {
|
|
11
|
+
if (isTauri()) {
|
|
12
|
+
try {
|
|
13
|
+
const plugin = await import("@tauri-apps/plugin-notification");
|
|
14
|
+
let granted = await plugin.isPermissionGranted();
|
|
15
|
+
if (!granted) {
|
|
16
|
+
const permission = await plugin.requestPermission();
|
|
17
|
+
granted = permission === "granted";
|
|
18
|
+
}
|
|
19
|
+
return granted ? "granted" : "denied";
|
|
20
|
+
} catch {
|
|
21
|
+
return "unsupported";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof window === "undefined" || !("Notification" in window)) {
|
|
26
|
+
return "unsupported";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (Notification.permission === "granted") {
|
|
30
|
+
return "granted";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Notification.permission === "denied") {
|
|
34
|
+
return "denied";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const permission = await Notification.requestPermission();
|
|
39
|
+
return permission === "granted" ? "granted" : "denied";
|
|
40
|
+
} catch {
|
|
41
|
+
return "denied";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type RouteAccessValidator = (route: string) => boolean
|
|
2
|
+
|
|
3
|
+
export class RouteAccess {
|
|
4
|
+
protected static readonly entries: Map<string, RouteAccessValidator | undefined> = new Map()
|
|
5
|
+
|
|
6
|
+
public static add(route: string, validator?: RouteAccessValidator) {
|
|
7
|
+
this.entries.set(route, validator)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public static exists(route: string): boolean {
|
|
11
|
+
return this.entries.has(route)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static getValidator(route: string): RouteAccessValidator | undefined {
|
|
15
|
+
return this.entries.get(route)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public static clear() {
|
|
19
|
+
this.entries.clear()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static delete(route: string) {
|
|
23
|
+
this.entries.delete(route)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static count(): number {
|
|
27
|
+
return this.entries.size
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static routes(): string[] {
|
|
31
|
+
return [...this.entries.keys()]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export class PublicRouteAccess extends RouteAccess {
|
|
37
|
+
|
|
38
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {AppConfig} from "../../domain/config/app.config";
|
|
2
|
+
import {AuthUserService} from "../../application/service/auth-user.service";
|
|
3
|
+
|
|
4
|
+
export type SocketEvent = "open" | "message" | "error" | "close";
|
|
5
|
+
export type SocketEventHandler = (payload: any) => void;
|
|
6
|
+
|
|
7
|
+
export class SocketService {
|
|
8
|
+
|
|
9
|
+
protected static _instance: WebSocket | undefined;
|
|
10
|
+
protected static _listeners: Map<SocketEvent, Set<SocketEventHandler>> = new Map();
|
|
11
|
+
protected static _reconnectAttempts: number = 0;
|
|
12
|
+
protected static _reconnectTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
+
protected static _shouldReconnect: boolean = true;
|
|
14
|
+
|
|
15
|
+
protected static get baseUrl(): string {
|
|
16
|
+
const apiHost = AppConfig.CORE_SOCKET_HOST as string;
|
|
17
|
+
return apiHost.replace(/^https?:\/\//, (protocol) => protocol === "https://" ? "wss://" : "ws://");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get instance(): WebSocket | undefined {
|
|
21
|
+
return this._instance;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static get isConnected(): boolean {
|
|
25
|
+
return !!this._instance && this._instance.readyState === WebSocket.OPEN;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static connect(path?: string): WebSocket {
|
|
29
|
+
if (this.isConnected) return this._instance!;
|
|
30
|
+
|
|
31
|
+
const url = this.buildUrl(path);
|
|
32
|
+
const socket = new WebSocket(url);
|
|
33
|
+
|
|
34
|
+
this._instance = socket;
|
|
35
|
+
this._shouldReconnect = true;
|
|
36
|
+
this._reconnectAttempts = 0;
|
|
37
|
+
|
|
38
|
+
socket.onopen = () => {
|
|
39
|
+
this._reconnectAttempts = 0;
|
|
40
|
+
this.dispatch("open", undefined);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
socket.onmessage = (event: MessageEvent) => {
|
|
44
|
+
let payload: any = event.data;
|
|
45
|
+
try {
|
|
46
|
+
payload = JSON.parse(event.data);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
this.dispatch("message", payload);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
socket.onerror = (event: Event) => {
|
|
53
|
+
this.dispatch("error", event);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
socket.onclose = (event: CloseEvent) => {
|
|
57
|
+
this._instance = undefined;
|
|
58
|
+
this.dispatch("close", event);
|
|
59
|
+
this.scheduleReconnect();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return socket;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static disconnect(): void {
|
|
66
|
+
this._shouldReconnect = false;
|
|
67
|
+
if (this._reconnectTimeout) clearTimeout(this._reconnectTimeout);
|
|
68
|
+
if (this._instance) this._instance.close();
|
|
69
|
+
this._instance = undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static send<T = any>(data: T | string): void {
|
|
73
|
+
if (!this.isConnected) return;
|
|
74
|
+
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
75
|
+
this._instance!.send(payload);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static on(event: SocketEvent, handler: SocketEventHandler): () => void {
|
|
79
|
+
const listeners = this._listeners.get(event) ?? new Set();
|
|
80
|
+
listeners.add(handler);
|
|
81
|
+
this._listeners.set(event, listeners);
|
|
82
|
+
return () => this.off(event, handler);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static off(event: SocketEvent, handler: SocketEventHandler): void {
|
|
86
|
+
this._listeners.get(event)?.delete(handler);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
protected static dispatch(event: SocketEvent, payload: any): void {
|
|
90
|
+
this._listeners.get(event)?.forEach((handler) => handler(payload));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected static scheduleReconnect(): void {
|
|
94
|
+
if (!this._shouldReconnect) return;
|
|
95
|
+
|
|
96
|
+
const attempts = Math.min(this._reconnectAttempts, 5);
|
|
97
|
+
const delay = Math.min(1000 * (2 ** attempts), 30000);
|
|
98
|
+
|
|
99
|
+
this._reconnectTimeout = setTimeout(() => {
|
|
100
|
+
this._reconnectAttempts++;
|
|
101
|
+
if (!this.isConnected) this.connect();
|
|
102
|
+
}, delay);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected static buildUrl(path?: string): string {
|
|
106
|
+
const token = AuthUserService.getToken();
|
|
107
|
+
const device = AuthUserService.getDevice();
|
|
108
|
+
const apiKey = AuthUserService.getApiKey();
|
|
109
|
+
const organization = AuthUserService.getCurrentOrganization();
|
|
110
|
+
|
|
111
|
+
const params: Record<string, string> = {};
|
|
112
|
+
if (token) params["token"] = token;
|
|
113
|
+
if (device) params["device"] = device;
|
|
114
|
+
if (apiKey) params["apiKey"] = apiKey;
|
|
115
|
+
if (organization) params["organizationId"] = organization.id;
|
|
116
|
+
|
|
117
|
+
const query = new URLSearchParams(params).toString();
|
|
118
|
+
const base = `${this.baseUrl}${path ?? ""}`;
|
|
119
|
+
|
|
120
|
+
return query ? `${base}${base.includes("?") ? "&" : "?"}${query}` : base;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|