From 399b52a2722e0ecd710365dd7f8517325efeff7c Mon Sep 17 00:00:00 2001 From: ewandor Date: Mon, 9 Jan 2023 16:39:23 +0100 Subject: [PATCH] Installing ng-matero --- front/app/.eslintrc.json | 84 + front/app/.prettierignore | 3 + front/app/.prettierrc | 17 + front/app/.stylelintrc | 39 + front/app/.vscode/extensions.json | 21 +- front/app/.vscode/settings.json | 20 + front/app/LICENSE | 21 + front/app/angular.json | 20 +- front/app/package-lock.json | 6621 ++++++++++++++++- front/app/package.json | 41 +- front/app/proxy.config.js | 22 + front/app/src/app/app-routing.module.ts | 10 - front/app/src/app/app.component.html | 3 - front/app/src/app/app.component.spec.ts | 35 - front/app/src/app/app.component.ts | 16 +- front/app/src/app/app.module.ts | 59 +- front/app/src/app/auth/auth.component.html | 1 - front/app/src/app/auth/auth.component.spec.ts | 23 - front/app/src/app/auth/auth.component.ts | 10 - .../app/src/app/core/authentication/README.md | 17 + .../core/authentication/auth.guard.spec.ts | 56 + .../src/app/core/authentication/auth.guard.ts | 32 + .../core/authentication/auth.service.spec.ts | 133 + .../app/core/authentication/auth.service.ts | 79 + .../src/app/core/authentication/helpers.ts | 57 + .../app/src/app/core/authentication/index.ts | 9 + .../src/app/core/authentication/interface.ts | 20 + .../app/core/authentication/login.service.ts | 32 + .../authentication/token-factory.service.ts | 20 + .../core/authentication/token.service.spec.ts | 52 + .../app/core/authentication/token.service.ts | 99 + .../src/app/core/authentication/token.spec.ts | 41 + .../app/src/app/core/authentication/token.ts | 87 + front/app/src/app/core/authentication/user.ts | 14 + front/app/src/app/core/bootstrap/README.md | 3 + .../src/app/core/bootstrap/menu.service.ts | 150 + .../app/core/bootstrap/preloader.service.ts | 28 + .../core/bootstrap/sanctum.service.spec.ts | 78 + .../src/app/core/bootstrap/sanctum.service.ts | 38 + .../app/core/bootstrap/settings.service.ts | 34 + .../core/bootstrap/startup.service.spec.ts | 87 + .../src/app/core/bootstrap/startup.service.ts | 53 + .../core/bootstrap/translate-lang.service.ts | 33 + front/app/src/app/core/core.module.ts | 13 + front/app/src/app/core/index.ts | 16 + front/app/src/app/core/initializers.ts | 37 + front/app/src/app/core/interceptors/README.md | 3 + .../interceptors/base-url-interceptor.spec.ts | 47 + .../core/interceptors/base-url-interceptor.ts | 24 + .../core/interceptors/default-interceptor.ts | 41 + .../interceptors/error-interceptor.spec.ts | 75 + .../core/interceptors/error-interceptor.ts | 60 + front/app/src/app/core/interceptors/index.ts | 31 + .../core/interceptors/logging-interceptor.ts | 30 + .../app/core/interceptors/noop-interceptor.ts | 10 + .../interceptors/sanctum-interceptor.spec.ts | 98 + .../core/interceptors/sanctum-interceptor.ts | 22 + .../interceptors/settings-interceptor.spec.ts | 33 + .../core/interceptors/settings-interceptor.ts | 17 + .../interceptors/token-interceptor.spec.ts | 116 + .../core/interceptors/token-interceptor.ts | 71 + front/app/src/app/core/module-import-guard.ts | 7 + front/app/src/app/core/settings.ts | 23 + front/app/src/app/fake-login.service.ts | 34 + front/app/src/app/formly-config.module.ts | 53 + front/app/src/app/formly-templates.ts | 45 + front/app/src/app/formly-validations.ts | 53 + front/app/src/app/formly-wrappers.ts | 29 + .../app/src/app/material-extensions.module.ts | 66 + front/app/src/app/material.module.ts | 117 + .../routes/dashboard/dashboard.component.html | 1 + .../dashboard/dashboard.component.scss} | 0 .../routes/dashboard/dashboard.component.ts | 13 + .../src/app/routes/routes-routing.module.ts | 48 + front/app/src/app/routes/routes.module.ts | 26 + .../src/app/routes/sessions/403.component.ts | 13 + .../src/app/routes/sessions/404.component.ts | 13 + .../src/app/routes/sessions/500.component.ts | 14 + .../sessions/login/login.component.html | 45 + .../sessions/login/login.component.scss | 0 .../routes/sessions/login/login.component.ts | 58 + .../sessions/register/register.component.html | 52 + .../sessions/register/register.component.scss | 0 .../sessions/register/register.component.ts | 39 + .../breadcrumb/breadcrumb.component.html | 12 + .../breadcrumb/breadcrumb.component.scss | 33 + .../breadcrumb/breadcrumb.component.ts | 33 + .../error-code/_error-code-theme.scss | 11 + .../components/error-code/_long-shadow.scss | 55 + .../error-code/error-code.component.html | 6 + .../error-code/error-code.component.scss | 32 + .../error-code/error-code.component.ts | 13 + .../page-header/page-header.component.html | 4 + .../page-header/page-header.component.scss | 18 + .../page-header/page-header.component.ts | 46 + .../directives/disable-control.directive.ts | 14 + .../shared/in-mem/in-mem-data.service.spec.ts | 16 + .../app/shared/in-mem/in-mem-data.service.ts | 209 + front/app/src/app/shared/index.ts | 12 + .../app/src/app/shared/pipes/safe-url.pipe.ts | 10 + .../app/shared/pipes/to-observable.pipe.ts | 9 + .../shared/services/directionality.service.ts | 22 + .../app/shared/services/message.service.ts | 16 + .../shared/services/paginator-i18n.service.ts | 45 + .../app/shared/services/storage.service.ts | 53 + front/app/src/app/shared/shared.module.ts | 51 + front/app/src/app/shared/utils/colors.ts | 610 ++ front/app/src/app/shared/utils/icons.ts | 1072 +++ .../admin-layout/admin-layout.component.html | 34 + .../admin-layout/admin-layout.component.scss | 160 + .../admin-layout/admin-layout.component.ts | 163 + .../auth-layout/auth-layout.component.html | 3 + .../auth-layout/auth-layout.component.scss | 26 + .../auth-layout/auth-layout.component.ts | 9 + .../src/app/theme/header/_header-theme.scss | 11 + .../app/theme/header/header.component.html | 27 + .../app/theme/header/header.component.scss | 8 + .../src/app/theme/header/header.component.ts | 33 + .../sidebar-notice.component.html | 4 + .../sidebar-notice.component.scss | 5 + .../sidebar-notice.component.ts | 8 + .../src/app/theme/sidebar/_sidebar-theme.scss | 17 + .../app/theme/sidebar/sidebar.component.html | 13 + .../app/theme/sidebar/sidebar.component.scss | 37 + .../app/theme/sidebar/sidebar.component.ts | 16 + .../theme/sidebar/user-panel.component.scss | 48 + .../app/theme/sidebar/user-panel.component.ts | 40 + .../app/theme/sidemenu/_sidemenu-theme.scss | 64 + .../sidemenu/nav-accordion-item.directive.ts | 43 + .../nav-accordion-toggle.directive.ts | 18 + .../theme/sidemenu/nav-accordion.directive.ts | 53 + .../theme/sidemenu/sidemenu.component.html | 70 + .../theme/sidemenu/sidemenu.component.scss | 125 + .../app/theme/sidemenu/sidemenu.component.ts | 19 + front/app/src/app/theme/style.scss | 3 + front/app/src/app/theme/style/_badge.scss | 14 + .../app/src/app/theme/style/_breakpoints.scss | 36 + front/app/src/app/theme/style/_colors.scss | 40 + front/app/src/app/theme/style/_misc.scss | 25 + .../src/app/theme/style/_reboot-theme.scss | 27 + front/app/src/app/theme/style/_reboot.scss | 48 + front/app/src/app/theme/style/_rtl.scss | 5 + front/app/src/app/theme/style/_scrollbar.scss | 44 + .../app/src/app/theme/style/_transitions.scss | 15 + front/app/src/app/theme/style/_variables.scss | 66 + front/app/src/app/theme/theme.module.ts | 46 + .../src/app/theme/topmenu/_topmenu-theme.scss | 32 + .../topmenu/topmenu-panel.component.html | 38 + .../theme/topmenu/topmenu-panel.component.ts | 82 + .../app/theme/topmenu/topmenu.component.html | 53 + .../app/theme/topmenu/topmenu.component.scss | 77 + .../app/theme/topmenu/topmenu.component.ts | 57 + .../app/theme/widgets/branding.component.ts | 20 + .../src/app/theme/widgets/github.component.ts | 26 + .../theme/widgets/notification.component.ts | 22 + .../app/theme/widgets/translate.component.ts | 34 + .../src/app/theme/widgets/user.component.ts | 56 + front/app/src/assets/data/dashboard.json | 28 + front/app/src/assets/data/menu.json | 37 + front/app/src/assets/fonts/Material_Icons.css | 23 + .../flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 | Bin 0 -> 60840 bytes front/app/src/assets/i18n/en-US.json | 145 + front/app/src/assets/i18n/zh-CN.json | 145 + front/app/src/assets/i18n/zh-TW.json | 145 + .../app/src/assets/images/avatar-default.jpg | Bin 0 -> 11400 bytes front/app/src/assets/images/avatar.jpg | Bin 0 -> 28421 bytes front/app/src/assets/images/matero.png | Bin 0 -> 9782 bytes .../app/src/environments/environment.prod.ts | 5 + front/app/src/environments/environment.ts | 18 + front/app/src/index.html | 67 +- front/app/src/styles.css | 3 - front/app/src/styles.scss | 7 + front/app/src/styles/_app-theme.scss | 28 + front/app/src/styles/_custom.scss | 2 + front/app/src/styles/_grid.scss | 1 + front/app/src/styles/_helpers.scss | 1 + front/app/src/styles/_plugins.scss | 2 + front/app/src/styles/_themes.scss | 46 + front/app/src/styles/custom/_material.scss | 18 + front/app/src/styles/custom/_mixins.scss | 5 + front/app/src/styles/custom/_table-theme.scss | 40 + front/app/src/styles/custom/_table.scss | 7 + front/app/src/styles/grid/_grid.scss | 40 + front/app/src/styles/grid/_mixins.scss | 79 + front/app/src/styles/grid/_variables.scss | 18 + front/app/src/styles/helpers/_alignment.scss | 13 + front/app/src/styles/helpers/_api.scss | 47 + front/app/src/styles/helpers/_border.scss | 33 + front/app/src/styles/helpers/_cursor.scss | 13 + front/app/src/styles/helpers/_display.scss | 13 + front/app/src/styles/helpers/_flexbox.scss | 90 + front/app/src/styles/helpers/_functions.scss | 14 + front/app/src/styles/helpers/_icon.scss | 18 + front/app/src/styles/helpers/_image.scss | 13 + front/app/src/styles/helpers/_mixins.scss | 6 + front/app/src/styles/helpers/_overflow.scss | 23 + front/app/src/styles/helpers/_position.scss | 13 + front/app/src/styles/helpers/_rounded.scss | 33 + front/app/src/styles/helpers/_sizing.scss | 18 + front/app/src/styles/helpers/_spacing.scss | 113 + front/app/src/styles/helpers/_text.scss | 49 + front/app/src/styles/helpers/_variables.scss | 83 + front/app/src/styles/plugins/_ngx-formly.scss | 3 + front/app/src/styles/plugins/_ngx-toastr.scss | 52 + .../app/src/styles/plugins/_photoviewer.scss | 3 + front/app/src/typings.d.ts | 1 + front/app/tsconfig.app.json | 11 +- front/app/tsconfig.json | 32 +- front/app/tsconfig.spec.json | 11 +- 209 files changed, 15574 insertions(+), 174 deletions(-) create mode 100644 front/app/.eslintrc.json create mode 100644 front/app/.prettierignore create mode 100644 front/app/.prettierrc create mode 100644 front/app/.stylelintrc create mode 100644 front/app/.vscode/settings.json create mode 100644 front/app/LICENSE create mode 100644 front/app/proxy.config.js delete mode 100644 front/app/src/app/app-routing.module.ts delete mode 100644 front/app/src/app/app.component.html delete mode 100644 front/app/src/app/app.component.spec.ts delete mode 100644 front/app/src/app/auth/auth.component.html delete mode 100644 front/app/src/app/auth/auth.component.spec.ts delete mode 100644 front/app/src/app/auth/auth.component.ts create mode 100644 front/app/src/app/core/authentication/README.md create mode 100644 front/app/src/app/core/authentication/auth.guard.spec.ts create mode 100644 front/app/src/app/core/authentication/auth.guard.ts create mode 100644 front/app/src/app/core/authentication/auth.service.spec.ts create mode 100644 front/app/src/app/core/authentication/auth.service.ts create mode 100644 front/app/src/app/core/authentication/helpers.ts create mode 100644 front/app/src/app/core/authentication/index.ts create mode 100644 front/app/src/app/core/authentication/interface.ts create mode 100644 front/app/src/app/core/authentication/login.service.ts create mode 100644 front/app/src/app/core/authentication/token-factory.service.ts create mode 100644 front/app/src/app/core/authentication/token.service.spec.ts create mode 100644 front/app/src/app/core/authentication/token.service.ts create mode 100644 front/app/src/app/core/authentication/token.spec.ts create mode 100644 front/app/src/app/core/authentication/token.ts create mode 100644 front/app/src/app/core/authentication/user.ts create mode 100644 front/app/src/app/core/bootstrap/README.md create mode 100644 front/app/src/app/core/bootstrap/menu.service.ts create mode 100644 front/app/src/app/core/bootstrap/preloader.service.ts create mode 100644 front/app/src/app/core/bootstrap/sanctum.service.spec.ts create mode 100644 front/app/src/app/core/bootstrap/sanctum.service.ts create mode 100644 front/app/src/app/core/bootstrap/settings.service.ts create mode 100644 front/app/src/app/core/bootstrap/startup.service.spec.ts create mode 100644 front/app/src/app/core/bootstrap/startup.service.ts create mode 100644 front/app/src/app/core/bootstrap/translate-lang.service.ts create mode 100644 front/app/src/app/core/core.module.ts create mode 100644 front/app/src/app/core/index.ts create mode 100644 front/app/src/app/core/initializers.ts create mode 100644 front/app/src/app/core/interceptors/README.md create mode 100644 front/app/src/app/core/interceptors/base-url-interceptor.spec.ts create mode 100644 front/app/src/app/core/interceptors/base-url-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/default-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/error-interceptor.spec.ts create mode 100644 front/app/src/app/core/interceptors/error-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/index.ts create mode 100644 front/app/src/app/core/interceptors/logging-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/noop-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts create mode 100644 front/app/src/app/core/interceptors/sanctum-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/settings-interceptor.spec.ts create mode 100644 front/app/src/app/core/interceptors/settings-interceptor.ts create mode 100644 front/app/src/app/core/interceptors/token-interceptor.spec.ts create mode 100644 front/app/src/app/core/interceptors/token-interceptor.ts create mode 100644 front/app/src/app/core/module-import-guard.ts create mode 100644 front/app/src/app/core/settings.ts create mode 100644 front/app/src/app/fake-login.service.ts create mode 100644 front/app/src/app/formly-config.module.ts create mode 100644 front/app/src/app/formly-templates.ts create mode 100644 front/app/src/app/formly-validations.ts create mode 100644 front/app/src/app/formly-wrappers.ts create mode 100644 front/app/src/app/material-extensions.module.ts create mode 100644 front/app/src/app/material.module.ts create mode 100644 front/app/src/app/routes/dashboard/dashboard.component.html rename front/app/src/app/{auth/auth.component.css => routes/dashboard/dashboard.component.scss} (100%) create mode 100644 front/app/src/app/routes/dashboard/dashboard.component.ts create mode 100644 front/app/src/app/routes/routes-routing.module.ts create mode 100644 front/app/src/app/routes/routes.module.ts create mode 100644 front/app/src/app/routes/sessions/403.component.ts create mode 100644 front/app/src/app/routes/sessions/404.component.ts create mode 100644 front/app/src/app/routes/sessions/500.component.ts create mode 100644 front/app/src/app/routes/sessions/login/login.component.html create mode 100644 front/app/src/app/routes/sessions/login/login.component.scss create mode 100644 front/app/src/app/routes/sessions/login/login.component.ts create mode 100644 front/app/src/app/routes/sessions/register/register.component.html create mode 100644 front/app/src/app/routes/sessions/register/register.component.scss create mode 100644 front/app/src/app/routes/sessions/register/register.component.ts create mode 100644 front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html create mode 100644 front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss create mode 100644 front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts create mode 100644 front/app/src/app/shared/components/error-code/_error-code-theme.scss create mode 100644 front/app/src/app/shared/components/error-code/_long-shadow.scss create mode 100644 front/app/src/app/shared/components/error-code/error-code.component.html create mode 100644 front/app/src/app/shared/components/error-code/error-code.component.scss create mode 100644 front/app/src/app/shared/components/error-code/error-code.component.ts create mode 100644 front/app/src/app/shared/components/page-header/page-header.component.html create mode 100644 front/app/src/app/shared/components/page-header/page-header.component.scss create mode 100644 front/app/src/app/shared/components/page-header/page-header.component.ts create mode 100644 front/app/src/app/shared/directives/disable-control.directive.ts create mode 100644 front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts create mode 100644 front/app/src/app/shared/in-mem/in-mem-data.service.ts create mode 100644 front/app/src/app/shared/index.ts create mode 100644 front/app/src/app/shared/pipes/safe-url.pipe.ts create mode 100644 front/app/src/app/shared/pipes/to-observable.pipe.ts create mode 100644 front/app/src/app/shared/services/directionality.service.ts create mode 100644 front/app/src/app/shared/services/message.service.ts create mode 100644 front/app/src/app/shared/services/paginator-i18n.service.ts create mode 100644 front/app/src/app/shared/services/storage.service.ts create mode 100644 front/app/src/app/shared/shared.module.ts create mode 100644 front/app/src/app/shared/utils/colors.ts create mode 100644 front/app/src/app/shared/utils/icons.ts create mode 100644 front/app/src/app/theme/admin-layout/admin-layout.component.html create mode 100644 front/app/src/app/theme/admin-layout/admin-layout.component.scss create mode 100644 front/app/src/app/theme/admin-layout/admin-layout.component.ts create mode 100644 front/app/src/app/theme/auth-layout/auth-layout.component.html create mode 100644 front/app/src/app/theme/auth-layout/auth-layout.component.scss create mode 100644 front/app/src/app/theme/auth-layout/auth-layout.component.ts create mode 100644 front/app/src/app/theme/header/_header-theme.scss create mode 100644 front/app/src/app/theme/header/header.component.html create mode 100644 front/app/src/app/theme/header/header.component.scss create mode 100644 front/app/src/app/theme/header/header.component.ts create mode 100644 front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html create mode 100644 front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss create mode 100644 front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts create mode 100644 front/app/src/app/theme/sidebar/_sidebar-theme.scss create mode 100644 front/app/src/app/theme/sidebar/sidebar.component.html create mode 100644 front/app/src/app/theme/sidebar/sidebar.component.scss create mode 100644 front/app/src/app/theme/sidebar/sidebar.component.ts create mode 100644 front/app/src/app/theme/sidebar/user-panel.component.scss create mode 100644 front/app/src/app/theme/sidebar/user-panel.component.ts create mode 100644 front/app/src/app/theme/sidemenu/_sidemenu-theme.scss create mode 100644 front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts create mode 100644 front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts create mode 100644 front/app/src/app/theme/sidemenu/nav-accordion.directive.ts create mode 100644 front/app/src/app/theme/sidemenu/sidemenu.component.html create mode 100644 front/app/src/app/theme/sidemenu/sidemenu.component.scss create mode 100644 front/app/src/app/theme/sidemenu/sidemenu.component.ts create mode 100644 front/app/src/app/theme/style.scss create mode 100644 front/app/src/app/theme/style/_badge.scss create mode 100644 front/app/src/app/theme/style/_breakpoints.scss create mode 100644 front/app/src/app/theme/style/_colors.scss create mode 100644 front/app/src/app/theme/style/_misc.scss create mode 100644 front/app/src/app/theme/style/_reboot-theme.scss create mode 100644 front/app/src/app/theme/style/_reboot.scss create mode 100644 front/app/src/app/theme/style/_rtl.scss create mode 100644 front/app/src/app/theme/style/_scrollbar.scss create mode 100644 front/app/src/app/theme/style/_transitions.scss create mode 100644 front/app/src/app/theme/style/_variables.scss create mode 100644 front/app/src/app/theme/theme.module.ts create mode 100644 front/app/src/app/theme/topmenu/_topmenu-theme.scss create mode 100644 front/app/src/app/theme/topmenu/topmenu-panel.component.html create mode 100644 front/app/src/app/theme/topmenu/topmenu-panel.component.ts create mode 100644 front/app/src/app/theme/topmenu/topmenu.component.html create mode 100644 front/app/src/app/theme/topmenu/topmenu.component.scss create mode 100644 front/app/src/app/theme/topmenu/topmenu.component.ts create mode 100644 front/app/src/app/theme/widgets/branding.component.ts create mode 100644 front/app/src/app/theme/widgets/github.component.ts create mode 100644 front/app/src/app/theme/widgets/notification.component.ts create mode 100644 front/app/src/app/theme/widgets/translate.component.ts create mode 100644 front/app/src/app/theme/widgets/user.component.ts create mode 100644 front/app/src/assets/data/dashboard.json create mode 100644 front/app/src/assets/data/menu.json create mode 100644 front/app/src/assets/fonts/Material_Icons.css create mode 100644 front/app/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 create mode 100644 front/app/src/assets/i18n/en-US.json create mode 100644 front/app/src/assets/i18n/zh-CN.json create mode 100644 front/app/src/assets/i18n/zh-TW.json create mode 100644 front/app/src/assets/images/avatar-default.jpg create mode 100644 front/app/src/assets/images/avatar.jpg create mode 100644 front/app/src/assets/images/matero.png create mode 100644 front/app/src/environments/environment.prod.ts create mode 100644 front/app/src/environments/environment.ts create mode 100644 front/app/src/styles.scss create mode 100644 front/app/src/styles/_app-theme.scss create mode 100644 front/app/src/styles/_custom.scss create mode 100644 front/app/src/styles/_grid.scss create mode 100644 front/app/src/styles/_helpers.scss create mode 100644 front/app/src/styles/_plugins.scss create mode 100644 front/app/src/styles/_themes.scss create mode 100644 front/app/src/styles/custom/_material.scss create mode 100644 front/app/src/styles/custom/_mixins.scss create mode 100644 front/app/src/styles/custom/_table-theme.scss create mode 100644 front/app/src/styles/custom/_table.scss create mode 100644 front/app/src/styles/grid/_grid.scss create mode 100644 front/app/src/styles/grid/_mixins.scss create mode 100644 front/app/src/styles/grid/_variables.scss create mode 100644 front/app/src/styles/helpers/_alignment.scss create mode 100644 front/app/src/styles/helpers/_api.scss create mode 100644 front/app/src/styles/helpers/_border.scss create mode 100644 front/app/src/styles/helpers/_cursor.scss create mode 100644 front/app/src/styles/helpers/_display.scss create mode 100644 front/app/src/styles/helpers/_flexbox.scss create mode 100644 front/app/src/styles/helpers/_functions.scss create mode 100644 front/app/src/styles/helpers/_icon.scss create mode 100644 front/app/src/styles/helpers/_image.scss create mode 100644 front/app/src/styles/helpers/_mixins.scss create mode 100644 front/app/src/styles/helpers/_overflow.scss create mode 100644 front/app/src/styles/helpers/_position.scss create mode 100644 front/app/src/styles/helpers/_rounded.scss create mode 100644 front/app/src/styles/helpers/_sizing.scss create mode 100644 front/app/src/styles/helpers/_spacing.scss create mode 100644 front/app/src/styles/helpers/_text.scss create mode 100644 front/app/src/styles/helpers/_variables.scss create mode 100644 front/app/src/styles/plugins/_ngx-formly.scss create mode 100644 front/app/src/styles/plugins/_ngx-toastr.scss create mode 100644 front/app/src/styles/plugins/_photoviewer.scss create mode 100644 front/app/src/typings.d.ts diff --git a/front/app/.eslintrc.json b/front/app/.eslintrc.json new file mode 100644 index 00000000..1e1f03f4 --- /dev/null +++ b/front/app/.eslintrc.json @@ -0,0 +1,84 @@ +{ + "root": true, + "ignorePatterns": ["dist"], + "overrides": [ + { + "files": ["*.ts"], + "parserOptions": { + "project": ["tsconfig.json", "e2e/tsconfig.json"], + "createDefaultProgram": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@angular-eslint/recommended", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "dot-notation": ["warn"], + "max-len": [ + "warn", + { + "code": 100, + "ignoreComments": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ], + "object-shorthand": [ + "warn", + "always", + { + "avoidQuotes": true + } + ], + "quote-props": ["warn", "consistent-as-needed"], + "quotes": [ + "warn", + "single", + { + "allowTemplateLiterals": true + } + ], + "semi": ["warn", "always"], + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": "off", + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "style": "kebab-case" + } + ], + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "style": "camelCase" + } + ], + "@angular-eslint/no-empty-lifecycle-method": "off" + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@angular-eslint/template/recommended"], + "rules": {} + }, + { + "files": ["*.js"], + "parserOptions": { + "ecmaVersion": 11 + }, + "env": { + "node": true, + "amd": true + }, + "extends": ["eslint:recommended"], + "rules": {} + } + ] +} diff --git a/front/app/.prettierignore b/front/app/.prettierignore new file mode 100644 index 00000000..03eef2fc --- /dev/null +++ b/front/app/.prettierignore @@ -0,0 +1,3 @@ +# add files you wish to ignore here +dist/ +node_modules/ diff --git a/front/app/.prettierrc b/front/app/.prettierrc new file mode 100644 index 00000000..621831f0 --- /dev/null +++ b/front/app/.prettierrc @@ -0,0 +1,17 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 100, + "proseWrap": "preserve", + "quoteProps": "consistent", + "requirePragma": false, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": false +} diff --git a/front/app/.stylelintrc b/front/app/.stylelintrc new file mode 100644 index 00000000..020b3d2a --- /dev/null +++ b/front/app/.stylelintrc @@ -0,0 +1,39 @@ +{ + "ignoreFiles": [ + "dist/**", + "schematics/**" + ], + "extends": [ + "stylelint-config-standard", + "stylelint-config-recommended-scss", + "stylelint-config-rational-order" + ], + "plugins": [ + "stylelint-order" + ], + "rules": { + "alpha-value-notation": null, + "annotation-no-unknown": null, + "at-rule-empty-line-before": null, + "color-function-notation": "legacy", + "function-no-unknown": null, + "no-descending-specificity": null, + "no-empty-source": null, + "number-leading-zero": "never", + "selector-pseudo-element-no-unknown": [ + true, + { + "ignorePseudoElements": [ + "ng-deep" + ] + } + ], + "selector-class-pattern": null, + "selector-type-no-unknown": null, + "string-quotes": "single", + "value-keyword-case": null, + "scss/at-extend-no-missing-placeholder": null, + "scss/comment-no-empty": null, + "scss/operator-no-unspaced": null + } +} diff --git a/front/app/.vscode/extensions.json b/front/app/.vscode/extensions.json index 77b37457..9023dc68 100644 --- a/front/app/.vscode/extensions.json +++ b/front/app/.vscode/extensions.json @@ -1,4 +1,17 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 - "recommendations": ["angular.ng-template"] -} +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "angular.ng-template", + "cyrilletuzi.angular-schematics", + "apk27.ngx-translate-lookup", + "esbenp.prettier-vscode", + "stylelint.vscode-stylelint", + "syler.sass-indented", + "editorconfig.editorconfig", + "box-of-hats.quick-material-icons", + "mrmlnc.vscode-scss" + ] +} diff --git a/front/app/.vscode/settings.json b/front/app/.vscode/settings.json new file mode 100644 index 00000000..b137fcb9 --- /dev/null +++ b/front/app/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "editor.rulers": [100], + "html.format.wrapLineLength": 100, + "html.format.wrapAttributes": "preserve-aligned", + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + "source.fixAll.stylelint": true + }, + "ngx-translate.lookup.resourcesType": "json", + "ngx-translate.lookup.resourcesPath": "src/assets/i18n/zh-CN.json", +} diff --git a/front/app/LICENSE b/front/app/LICENSE new file mode 100644 index 00000000..8297943e --- /dev/null +++ b/front/app/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Zongbin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/front/app/angular.json b/front/app/angular.json index 00c1c0e2..06b8dc95 100644 --- a/front/app/angular.json +++ b/front/app/angular.json @@ -25,7 +25,7 @@ "src/assets" ], "styles": [ - "@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss", "src/styles.css" ], "scripts": [] @@ -36,12 +36,12 @@ { "type": "initial", "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumError": "4mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumError": "16kb" } ], "outputHashing": "all" @@ -65,6 +65,9 @@ }, "development": { "browserTarget": "app:build:development" + }, + "options": { + "proxyConfig": "proxy.config.js" } }, "defaultConfiguration": "development" @@ -88,11 +91,20 @@ "src/assets" ], "styles": [ - "@angular/material/prebuilt-themes/deeppurple-amber.css", + "src/styles.scss", "src/styles.css" ], "scripts": [] } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } } } } diff --git a/front/app/package-lock.json b/front/app/package-lock.json index 38b9dde9..d0f7d771 100644 --- a/front/app/package-lock.json +++ b/front/app/package-lock.json @@ -9,30 +9,60 @@ "version": "0.0.0", "dependencies": { "@angular/animations": "^15.0.0", - "@angular/cdk": "^15.0.4", + "@angular/cdk": "~15.0.3", "@angular/common": "^15.0.0", "@angular/compiler": "^15.0.0", "@angular/core": "^15.0.0", "@angular/forms": "^15.0.0", - "@angular/material": "^15.0.4", + "@angular/material": "~15.0.3", + "@angular/material-moment-adapter": "~15.0.3", "@angular/platform-browser": "^15.0.0", "@angular/platform-browser-dynamic": "^15.0.0", "@angular/router": "^15.0.0", + "@ng-matero/extensions": "^15.0.1", + "@ng-matero/extensions-moment-adapter": "^15.0.0", + "@ngx-formly/core": "^6.0.4", + "@ngx-formly/material": "^6.0.4", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "moment": "^2.29.4", + "ng-matero": "^15.1.1", + "ngx-permissions": "^14.0.0", + "ngx-progressbar": "^9.0.0", + "ngx-toastr": "^16.0.1", + "photoviewer": "^3.6.6", "rxjs": "~7.5.0", + "screenfull": "^6.0.2", "tslib": "^2.3.0", "zone.js": "~0.12.0" }, "devDependencies": { "@angular-devkit/build-angular": "^15.0.5", + "@angular-eslint/builder": "^15.1.0", + "@angular-eslint/eslint-plugin": "^15.1.0", + "@angular-eslint/eslint-plugin-template": "^15.1.0", + "@angular-eslint/schematics": "^15.1.0", + "@angular-eslint/template-parser": "^15.1.0", "@angular/cli": "~15.0.5", "@angular/compiler-cli": "^15.0.0", "@types/jasmine": "~4.3.0", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", + "eslint": "^8.30.0", "jasmine-core": "~4.5.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "parse5": "^7.1.2", + "prettier": "^2.8.1", + "stylelint": "^14.16.0", + "stylelint-config-rational-order": "^0.1.2", + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-order": "^5.0.0", + "stylelint-scss": "^4.3.0", "typescript": "~4.8.2" } }, @@ -326,6 +356,141 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/@angular-eslint/builder": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-15.1.0.tgz", + "integrity": "sha512-MoPeJv4a1wSoFj8fVA01hFb+QQke2t74CSVuc6o4EqkWI0tYMM1Wg19fPtTZnj4spkGA82j2mf/tazKGRe/nrw==", + "dev": true, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-15.1.0.tgz", + "integrity": "sha512-zcOx+PnYuVDIG3wd/JVzCYdEUarKGtgIcN4iU9ZF+BVk5e8i9cbD3U8U3EDJKbrrokbFl9GBBJMCOa6XYTGJwQ==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-15.1.0.tgz", + "integrity": "sha512-3RRDnxaCEI5DdKq3hipXvrxctPPssrUXnNbgczJRIJ3cssr4ndobCSNqUSepA6vWj5mWe7w+nnh4vgfhZ5keig==", + "dev": true, + "dependencies": { + "@angular-eslint/utils": "15.1.0", + "@typescript-eslint/utils": "5.44.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-15.1.0.tgz", + "integrity": "sha512-WofUNiLcO/oprnzswkF+u1PC6ulmqB/m7fNKMMnbExMYuK1P38gjp59FW7E+2Ivz+A4/8a5xV+U+cy3oRgh4NQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "15.1.0", + "@angular-eslint/utils": "15.1.0", + "@typescript-eslint/type-utils": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "aria-query": "5.1.3", + "axobject-query": "3.1.1" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-15.1.0.tgz", + "integrity": "sha512-BJm7FFVCad8TV8Gtwq+FbgtLGvjJDlpt5Rne1hCd4nCr8vlQZxSWVwnTHRkAs+qd5dYn3p7bGcKZxEZzeVkWjA==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "15.1.0", + "@angular-eslint/eslint-plugin-template": "15.1.0", + "ignore": "5.2.0", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "peerDependencies": { + "@angular/cli": ">= 15.0.0 < 16.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-15.1.0.tgz", + "integrity": "sha512-ctcA7OAV1wwFByW1te3uZwzySuIRlo8NblG5yUtgU5BXt3nXwIDwoSr3tvI2dRHobNHcXVQcOFVzyOdXD/vsIg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "15.1.0", + "eslint-scope": "^7.0.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-15.1.0.tgz", + "integrity": "sha512-Vt59o7wq3UOgHSCrOaHg0SgxgbAGhG0ofNQwd7sLqNP2/w/90dWY2jwWXIVSuZ+BmfVj3wgNi3KujbSWJP1cfg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "15.1.0", + "@typescript-eslint/utils": "5.44.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, "node_modules/@angular/animations": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-15.0.4.tgz", @@ -356,30 +521,6 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/cdk/node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "optional": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@angular/cdk/node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "optional": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/@angular/cli": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.0.5.tgz", @@ -586,6 +727,19 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material-moment-adapter": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-15.0.4.tgz", + "integrity": "sha512-F1wVCGpplytmuQJrLjghnIZDVzs47n5w8WD5hJTGW5/dD4Bz1AIJE2G2rKGvqiV010ditHvjR6Hf8Ggo0ZIBpg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^15.0.0 || ^16.0.0", + "@angular/material": "15.0.4", + "moment": "^2.18.1" + } + }, "node_modules/@angular/platform-browser": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-15.0.4.tgz", @@ -2392,6 +2546,31 @@ "node": ">=0.1.90" } }, + "node_modules/@csstools/selector-specificity": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", + "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2", + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.5.0.tgz", + "integrity": "sha512-tlJpwF40DEQcfR/QF+wNMVyGMaO9FQp6Z1Wahj4Gk3CJQYHwA2xVG7iKDFdW6zuxZY9XWOpGcfNCTsX4McOsOg==", + "engines": { + "node": ">=10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -2433,12 +2612,179 @@ "node": ">=12" } }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3289,6 +3635,75 @@ "tslib": "^2.1.0" } }, + "node_modules/@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@mrmlnc/readdir-enhanced/node_modules/glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", + "dev": true + }, + "node_modules/@ng-matero/extensions": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions/-/extensions-15.0.1.tgz", + "integrity": "sha512-MuI+s48H0UEoij1WprcDLhY+FG21FxsXhhNWzcXaktP0ARQenG4PGtYx24blCHYA4wFhXLnGczr8rcf6MLRLJQ==", + "dependencies": { + "@ng-select/ng-select": "^10.0.0", + "ngx-color": "^8.0.0", + "photoviewer": "^3.6.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@angular/animations": ">=15.0.0", + "@angular/cdk": ">=15.0.0", + "@angular/common": ">=15.0.0", + "@angular/core": ">=15.0.0", + "@angular/material": ">=15.0.0" + } + }, + "node_modules/@ng-matero/extensions-moment-adapter": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@ng-matero/extensions-moment-adapter/-/extensions-moment-adapter-15.0.0.tgz", + "integrity": "sha512-NOxAb7vDxQjUznxt8HRSE6STF48sIoL9DgsKpaT+G4yli+pZUPUorMEDPDX760g7z653Sj3v2XmiTCOEr510gw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@angular/core": ">=15.0.0", + "@angular/material": ">=15.0.0", + "@angular/material-moment-adapter": ">=15.0.0", + "@ng-matero/extensions": ">=15.0.0", + "moment": "^2.29.4" + } + }, + "node_modules/@ng-select/ng-select": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-10.0.3.tgz", + "integrity": "sha512-Ma8pzKMI5TWnsKgOiONQLGeUeTko9gI6AtqpMMOVhrCktjtUSo9h5N17WomHRGtba9D7QviTZcR7UBhKOPwZ7g==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 12.20.0", + "npm": ">= 6.0.0" + }, + "peerDependencies": { + "@angular/common": "<16.0.0", + "@angular/core": "<16.0.0", + "@angular/forms": "<16.0.0" + } + }, "node_modules/@ngtools/webpack": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.0.5.tgz", @@ -3305,6 +3720,55 @@ "webpack": "^5.54.0" } }, + "node_modules/@ngx-formly/core": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@ngx-formly/core/-/core-6.0.4.tgz", + "integrity": "sha512-GH+uIwNivI7EmqIRcGwyWa+CXPBY3ihWi05foMsYV7ghADe2Ap6mHaYdKoejMItLoWz79CrV2pYfwrnjlE3KOQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/forms": ">=13.2.0", + "rxjs": "^6.5.3 || ^7.0.0" + } + }, + "node_modules/@ngx-formly/material": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@ngx-formly/material/-/material-6.0.4.tgz", + "integrity": "sha512-eJjLcR7Kueqg9KfyaPEjQADaF5qB4M4TrNgY0SJ6SWnJjE1hWQWcLvwKxo/7EX93ClD/1Z3ytZMRk1homPK8HA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/material": ">=13.0.0", + "@ngx-formly/core": "6.0.4" + } + }, + "node_modules/@ngx-translate/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-14.0.0.tgz", + "integrity": "sha512-UevdwNCXMRCdJv//0kC8h2eSfmi02r29xeE8E9gJ1Al4D4jEJ7eiLPdjslTMc21oJNGguqqWeEVjf64SFtvw2w==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=13.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-7.0.0.tgz", + "integrity": "sha512-j+NpXXlcGVdyUNyY/qsJrqqeAdJdizCd+GKh3usXExSqy1aE9866jlAIL+xrfDU4w+LiMoma5pgE4emvFebZmA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@ngx-translate/core": ">=14.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3659,6 +4123,16 @@ "@types/range-parser": "*" } }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -3686,12 +4160,30 @@ "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", "dev": true }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3716,6 +4208,12 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.3.13", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", + "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", + "dev": true + }, "node_modules/@types/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", @@ -3744,6 +4242,33 @@ "@types/node": "*" } }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "node_modules/@types/vfile-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-2.0.0.tgz", + "integrity": "sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==", + "deprecated": "This is a stub types definition. vfile-message provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "vfile-message": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", @@ -3753,6 +4278,464 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz", + "integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/type-utils": "5.48.0", + "@typescript-eslint/utils": "5.48.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz", + "integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.48.0", + "@typescript-eslint/utils": "5.48.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz", + "integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", + "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", + "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", + "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", + "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", + "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", + "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz", + "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/typescript-estree": "5.44.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", + "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", + "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", + "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", + "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.48.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -3963,6 +4946,15 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -4181,12 +5173,123 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.13", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", @@ -4220,6 +5323,27 @@ "postcss": "^8.1.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, "node_modules/babel-loader": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", @@ -4301,12 +5425,52 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4558,6 +5722,26 @@ "node": ">=12" } }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4571,6 +5755,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4589,6 +5812,23 @@ "node": ">=6" } }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001442", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz", @@ -4605,6 +5845,16 @@ } ] }, + "node_modules/ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4619,6 +5869,46 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -4670,6 +5960,110 @@ "node": ">=6.0" } }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -4749,6 +6143,42 @@ "node": ">=6" } }, + "node_modules/clone-regexp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", + "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", + "dev": true, + "dependencies": { + "is-regexp": "^1.0.0", + "is-supported-regexp-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4773,6 +6203,12 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, "node_modules/colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", @@ -4791,6 +6227,12 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -4956,6 +6398,15 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/copy-webpack-plugin": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", @@ -5112,6 +6563,12 @@ "node": ">=8" } }, + "node_modules/critters/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/critters/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5153,6 +6610,15 @@ "node": ">= 8" } }, + "node_modules/css-functions-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "dev": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/css-loader": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", @@ -5219,6 +6685,18 @@ "node": ">=4" } }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -5251,6 +6729,89 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-equal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", + "integrity": "sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.2", + "get-intrinsic": "^1.1.3", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.1", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -5284,6 +6845,35 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -5360,6 +6950,18 @@ "node": ">=6" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -5413,6 +7015,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/domq.js": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/domq.js/-/domq.js-0.6.7.tgz", + "integrity": "sha512-WwRGORo/eYGf7v7YXZ3M6x/PEoxCsP3D0my7pnAVwtbfsKRvW6qioSdlLsy1MFzfwN1TM9oO9QJcvkE8ERYmlg==" + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -5427,6 +7034,18 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5581,6 +7200,31 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -5981,6 +7625,62 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -5994,6 +7694,325 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", + "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -6007,6 +8026,27 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -6099,6 +8139,161 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execall": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-1.0.0.tgz", + "integrity": "sha512-/J0Q8CvOvlAdpvhfkD/WnTQ4H1eU0exze2nFGPj/RSC7jpQ0NkKe2r28T5eMkhEEs+fzepMZNy1kVRKNlC04nQ==", + "dev": true, + "dependencies": { + "clone-regexp": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -6204,6 +8399,19 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -6218,6 +8426,58 @@ "node": ">=4" } }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6246,6 +8506,21 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -6282,6 +8557,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6369,6 +8656,19 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/flatted": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", @@ -6395,6 +8695,24 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6417,6 +8735,18 @@ "url": "https://www.patreon.com/infusion" } }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -6484,6 +8814,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", @@ -6544,6 +8883,15 @@ "node": ">=8.0.0" } }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6556,6 +8904,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/glob": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", @@ -6593,6 +8950,38 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6621,18 +9010,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6645,6 +9082,15 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -6654,6 +9100,18 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -6666,12 +9124,96 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hdr-histogram-js": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", @@ -6764,6 +9306,94 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-tags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", + "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -6999,6 +9629,15 @@ "node": ">=4" } }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -7017,6 +9656,12 @@ "node": ">=8" } }, + "node_modules/indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==", + "dev": true + }, "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -7159,12 +9804,99 @@ "node": ">= 10" } }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumeric": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", + "integrity": "sha512-ZmRL7++ZkcMOfDuWZuMJyIVLr2keE1o/DeNWh1EmgqGhUcV+9BIVsx0BcSBOHTZqzjs4+dISzr2KAeBEWGgXeA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7177,6 +9909,57 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -7189,6 +9972,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -7204,6 +10047,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7234,6 +10089,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -7249,6 +10114,15 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7258,6 +10132,39 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -7282,6 +10189,52 @@ "node": ">=0.10.0" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7294,6 +10247,64 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-supported-regexp-flag": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", + "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -7306,12 +10317,63 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", "dev": true }, + "node_modules/is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7506,6 +10568,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-sdsl": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", + "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7537,6 +10609,12 @@ "node": ">=4" } }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -7549,6 +10627,12 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -7825,6 +10909,12 @@ "node": ">= 8" } }, + "node_modules/known-css-properties": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", + "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "dev": true + }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", @@ -7918,6 +11008,28 @@ "node": ">=0.10.0" } }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -7941,6 +11053,43 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -7983,6 +11132,18 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -8085,6 +11246,29 @@ "node": ">=8.0" } }, + "node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "dev": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -8256,6 +11440,83 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdown-table": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "dev": true + }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-compact": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", + "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", + "dev": true, + "dependencies": { + "unist-util-visit": "^1.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8277,6 +11538,98 @@ "node": ">= 4.0.0" } }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -8362,6 +11715,15 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz", @@ -8408,6 +11770,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/minipass": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", @@ -8520,6 +11905,19 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -8532,6 +11930,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8569,6 +11975,40 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/needle": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", @@ -8625,6 +12065,66 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ng-matero": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/ng-matero/-/ng-matero-15.1.1.tgz", + "integrity": "sha512-wCyFN1dgsRUWTRw6czEc6uSHikbray4fdiwX2B0BmtqZ2sxxAJ5SkO6h0oehpar4i4hoUcFE50AReSnBdQ4wDw==" + }, + "node_modules/ngx-color": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-8.0.3.tgz", + "integrity": "sha512-tuLP+uIoDEu2m0bh711kb2P1M1bh/oIrOn8mJd9mb8xGL2v+OcokcxPmVvWRn0avMG1lXL53CjSlWXGkdV4CDA==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "material-colors": "^1.2.6", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0-0", + "@angular/core": ">=14.0.0-0" + } + }, + "node_modules/ngx-permissions": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/ngx-permissions/-/ngx-permissions-14.0.0.tgz", + "integrity": "sha512-jkqTwfomNdSOWsMyMJ2SJcxTge/1HQVuQMe+fm5//HbuT4xc1NjMinAb1W8Y+HHgY90sEam17ndDCxhfhSyTNQ==", + "dependencies": { + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@angular/core": ">=13 || >16", + "@angular/router": ">=13 || >16", + "rxjs": ">=7 || >9", + "zone.js": ">=0.11.4 || >0.12" + } + }, + "node_modules/ngx-progressbar": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-progressbar/-/ngx-progressbar-9.0.0.tgz", + "integrity": "sha512-QaDxKY3KNlOdzPgdVzoUZVemZIM4YHmn3XrzbyQHWUaD1Gd6fr3BOJ+tTMKEfrJzjFQte/lszAKCaTrHY0g95w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@angular/core": ">=13.0.0", + "@angular/router": ">=13.0.0", + "rxjs": ">=6.0.0" + } + }, + "node_modules/ngx-toastr": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-16.0.2.tgz", + "integrity": "sha512-J6SueNCaGwm/gpXdsG56UzMEAcuayYWEW6NmIrNoe5iP7lOUohg4xYXWipkbMH9wGWmLPD9gU8AufUVWMplCvg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0-0", + "@angular/core": ">=14.0.0-0", + "@angular/platform-browser": ">=14.0.0-0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -8824,6 +12324,12 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-selector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", + "integrity": "sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==", + "dev": true + }, "node_modules/npm-bundled": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", @@ -9132,6 +12638,12 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9141,6 +12653,97 @@ "node": ">=0.10.0" } }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -9150,6 +12753,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -9218,6 +12888,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -9499,6 +13186,20 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9527,10 +13228,16 @@ } }, "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } }, "node_modules/parse5-html-rewriting-stream": { "version": "6.0.1", @@ -9542,6 +13249,12 @@ "parse5-sax-parser": "^6.0.1" } }, + "node_modules/parse5-html-rewriting-stream/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", @@ -9551,6 +13264,12 @@ "parse5": "^6.0.1" } }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parse5-sax-parser": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-6.0.1.tgz", @@ -9560,6 +13279,24 @@ "parse5": "^6.0.1" } }, + "node_modules/parse5-sax-parser/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "devOptional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -9569,6 +13306,21 @@ "node": ">= 0.8" } }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9617,6 +13369,14 @@ "node": ">=8" } }, + "node_modules/photoviewer": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.6.6.tgz", + "integrity": "sha512-TYuxoEdlVkIngVnoCEO+CWjeTO/F4TOPDv9ic4zbVU8ZXMiDggUzMj7jv50Kl0n1Yks72hleujPjAfGmkl7n9w==", + "dependencies": { + "domq.js": "^0.6.7" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9640,7 +13400,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "optional": true, "engines": { "node": ">=6" } @@ -9671,6 +13430,15 @@ "node": ">=8" } }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss": { "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", @@ -9695,6 +13463,76 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "dependencies": { + "htmlparser2": "^3.10.0" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-jsx": { + "version": "0.36.4", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.4.tgz", + "integrity": "sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==", + "dev": true, + "dependencies": { + "@babel/core": ">=7.2.2" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">=6.14.4" + } + }, + "node_modules/postcss-less/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss-less/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss-loader": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.1.tgz", @@ -9717,6 +13555,26 @@ "webpack": "^5.0.0" } }, + "node_modules/postcss-markdown": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", + "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", + "dev": true, + "dependencies": { + "remark": "^10.0.1", + "unist-util-find-all-after": "^1.0.2" + }, + "peerDependencies": { + "postcss": ">=5.0.0", + "postcss-syntax": ">=0.36.0" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, "node_modules/postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", @@ -9776,6 +13634,151 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-reporter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/postcss-reporter/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-reporter/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss-reporter/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-reporter/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-sass": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.3.5.tgz", + "integrity": "sha512-B5z2Kob4xBxFjcufFnhQ2HqJQ2y/Zs/ic5EZbCywCkxKd756Q40cIQ/veRDwSrw1BF6+4wUgmpm0sBASqVi65A==", + "dev": true, + "dependencies": { + "gonzales-pe": "^4.2.3", + "postcss": "^7.0.1" + } + }, + "node_modules/postcss-sass/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/postcss-sass/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-sass/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz", + "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + } + ], + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.19" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", @@ -9789,12 +13792,54 @@ "node": ">=4" } }, + "node_modules/postcss-sorting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz", + "integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==", + "dev": true, + "peerDependencies": { + "postcss": "^8.3.9" + } + }, + "node_modules/postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true, + "peerDependencies": { + "postcss": ">=5.0.0" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", + "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -9923,6 +13968,15 @@ } ] }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10002,6 +14056,83 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -10028,6 +14159,19 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -10067,12 +14211,54 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regex-parser": { "version": "2.2.11", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/regexpu-core": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", @@ -10117,6 +14303,89 @@ "jsesc": "bin/jsesc" } }, + "node_modules/remark": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", + "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", + "dev": true, + "dependencies": { + "remark-parse": "^6.0.0", + "remark-stringify": "^6.0.0", + "unified": "^7.0.0" + } + }, + "node_modules/remark-parse": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", + "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", + "dev": true, + "dependencies": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "node_modules/remark-stringify": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", + "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", + "dev": true, + "dependencies": { + "ccount": "^1.0.0", + "is-alphanumeric": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "longest-streak": "^2.0.1", + "markdown-escapes": "^1.0.0", + "markdown-table": "^1.1.0", + "mdast-util-compact": "^1.0.0", + "parse-entities": "^1.0.2", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "stringify-entities": "^1.0.1", + "unherit": "^1.0.4", + "xtend": "^4.0.1" + } + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10167,6 +14436,13 @@ "node": ">=8" } }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, "node_modules/resolve-url-loader": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", @@ -10219,6 +14495,15 @@ "node": ">=8" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -10361,6 +14646,15 @@ } ] }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10453,6 +14747,17 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/screenfull": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-6.0.2.tgz", + "integrity": "sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -10669,6 +14974,42 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -10740,6 +15081,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -10750,6 +15141,215 @@ "npm": ">= 3.0.0" } }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/socket.io": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz", @@ -10876,6 +15476,20 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -10895,6 +15509,13 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -10964,6 +15585,27 @@ "wbuf": "^1.7.3" } }, + "node_modules/specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true, + "bin": { + "specificity": "bin/specificity" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11000,6 +15642,118 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -11046,6 +15800,18 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", + "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "dev": true, + "dependencies": { + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11058,6 +15824,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -11067,6 +15842,1256 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, + "node_modules/stylelint": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", + "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^7.1.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.1", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.26.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.19", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^2.3.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^4.0.2" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint-config-rational-order": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stylelint-config-rational-order/-/stylelint-config-rational-order-0.1.2.tgz", + "integrity": "sha512-Qo7ZQaihCwTqijfZg4sbdQQHtugOX/B1/fYh018EiDZHW+lkqH9uHOnsDwDPGZrYJuB6CoyI7MZh2ecw2dOkew==", + "dev": true, + "dependencies": { + "stylelint": "^9.10.1", + "stylelint-order": "^2.2.1" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/autoprefixer": { + "version": "9.8.8", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", + "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "picocolors": "^0.2.1", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "dependencies": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/file-entry-cache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-4.0.0.tgz", + "integrity": "sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/globby/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/html-tags": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", + "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/import-fresh/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/known-css-properties": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.11.0.tgz", + "integrity": "sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/path-type/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "dependencies": { + "postcss": "^7.0.26" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-scss": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.1.1.tgz", + "integrity": "sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/postcss-sorting": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-4.1.0.tgz", + "integrity": "sha512-r4T2oQd1giURJdHQ/RMb72dKZCuLOdWx2B/XhXN1Y1ZdnwXsKH896Qz6vD4tFy9xSjpKNYhlZoJmWyhH/7JUQw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.4", + "postcss": "^7.0.0" + }, + "engines": { + "node": ">=6.14.3" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/stylelint": { + "version": "9.10.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-9.10.1.tgz", + "integrity": "sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==", + "dev": true, + "dependencies": { + "autoprefixer": "^9.0.0", + "balanced-match": "^1.0.0", + "chalk": "^2.4.1", + "cosmiconfig": "^5.0.0", + "debug": "^4.0.0", + "execall": "^1.0.0", + "file-entry-cache": "^4.0.0", + "get-stdin": "^6.0.0", + "global-modules": "^2.0.0", + "globby": "^9.0.0", + "globjoin": "^0.1.4", + "html-tags": "^2.0.0", + "ignore": "^5.0.4", + "import-lazy": "^3.1.0", + "imurmurhash": "^0.1.4", + "known-css-properties": "^0.11.0", + "leven": "^2.1.0", + "lodash": "^4.17.4", + "log-symbols": "^2.0.0", + "mathml-tag-names": "^2.0.1", + "meow": "^5.0.0", + "micromatch": "^3.1.10", + "normalize-selector": "^0.2.0", + "pify": "^4.0.0", + "postcss": "^7.0.13", + "postcss-html": "^0.36.0", + "postcss-jsx": "^0.36.0", + "postcss-less": "^3.1.0", + "postcss-markdown": "^0.36.0", + "postcss-media-query-parser": "^0.2.3", + "postcss-reporter": "^6.0.0", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^4.0.0", + "postcss-sass": "^0.3.5", + "postcss-scss": "^2.0.0", + "postcss-selector-parser": "^3.1.0", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^3.3.0", + "resolve-from": "^4.0.0", + "signal-exit": "^3.0.2", + "slash": "^2.0.0", + "specificity": "^0.4.1", + "string-width": "^3.0.0", + "style-search": "^0.1.0", + "sugarss": "^2.0.0", + "svg-tags": "^1.0.0", + "table": "^5.0.0" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/stylelint-order": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-2.2.1.tgz", + "integrity": "sha512-019KBV9j8qp1MfBjJuotse6MgaZqGVtXMc91GU9MsS9Feb+jYUvUU3Z8XiClqPdqJZQ0ryXQJGg3U3PcEjXwfg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10", + "postcss": "^7.0.2", + "postcss-sorting": "^4.1.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "stylelint": "^9.10.1 || ^10.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/stylelint/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/stylelint-config-rational-order/node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylelint-config-rational-order/node_modules/yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz", + "integrity": "sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.10.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-8.0.0.tgz", + "integrity": "sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==", + "dev": true, + "dependencies": { + "postcss-scss": "^4.0.2", + "stylelint-config-recommended": "^9.0.0", + "stylelint-scss": "^4.0.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^14.10.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-config-standard": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", + "integrity": "sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^9.0.0" + }, + "peerDependencies": { + "stylelint": "^14.14.0" + } + }, + "node_modules/stylelint-order": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-5.0.0.tgz", + "integrity": "sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==", + "dev": true, + "dependencies": { + "postcss": "^8.3.11", + "postcss-sorting": "^7.0.1" + }, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, + "node_modules/stylelint-scss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.3.0.tgz", + "integrity": "sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.6", + "postcss-value-parser": "^4.1.0" + }, + "peerDependencies": { + "stylelint": "^14.5.1" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.2" + } + }, + "node_modules/sugarss/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/sugarss/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/sugarss/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -11079,6 +17104,40 @@ "node": ">=4" } }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -11091,6 +17150,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -11100,6 +17165,22 @@ "node": ">=0.10" } }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -11352,6 +17433,51 @@ "node": ">=4" } }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11382,11 +17508,79 @@ "tree-kill": "cli.js" } }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", + "dev": true + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -11450,6 +17644,20 @@ "node": "*" } }, + "node_modules/unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -11490,6 +17698,61 @@ "node": ">=4" } }, + "node_modules/unified": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^3.0.0", + "x-is-string": "^0.1.0" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==", + "dev": true + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", @@ -11514,6 +17777,62 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/unist-util-find-all-after": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz", + "integrity": "sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==", + "dev": true, + "dependencies": { + "unist-util-is": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "node_modules/unist-util-remove-position": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "dev": true, + "dependencies": { + "unist-util-visit": "^1.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true + }, + "node_modules/unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "dependencies": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "dependencies": { + "unist-util-is": "^3.0.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -11532,6 +17851,54 @@ "node": ">= 0.8" } }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -11567,6 +17934,22 @@ "punycode": "^2.1.0" } }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11591,6 +17974,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -11622,6 +18011,64 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", + "dev": true, + "dependencies": { + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + } + }, + "node_modules/vfile-location": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.3.tgz", + "integrity": "sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/unist-util-stringify-position": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile/node_modules/vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "dependencies": { + "unist-util-stringify-position": "^1.1.1" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -11935,6 +18382,57 @@ "which": "bin/which" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -11950,6 +18448,15 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -12006,6 +18513,31 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/ws": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", @@ -12027,6 +18559,21 @@ } } }, + "node_modules/x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -12078,6 +18625,18 @@ "node": ">=12" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zone.js": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.12.0.tgz", diff --git a/front/app/package.json b/front/app/package.json index 67c9b726..89e35be2 100644 --- a/front/app/package.json +++ b/front/app/package.json @@ -6,35 +6,70 @@ "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "build:prod": "ng build --prod", + "lint": "npm run lint:ts && npm run lint:scss", + "lint:ts": "eslint \"src/**/*.ts\" --fix", + "lint:scss": "stylelint \"src/**/*.scss\" --fix", + "hmr": "ng serve --hmr --disable-host-check" }, "private": true, "dependencies": { "@angular/animations": "^15.0.0", - "@angular/cdk": "^15.0.4", + "@angular/cdk": "~15.0.3", "@angular/common": "^15.0.0", "@angular/compiler": "^15.0.0", "@angular/core": "^15.0.0", "@angular/forms": "^15.0.0", - "@angular/material": "^15.0.4", + "@angular/material": "~15.0.3", + "@angular/material-moment-adapter": "~15.0.3", "@angular/platform-browser": "^15.0.0", "@angular/platform-browser-dynamic": "^15.0.0", "@angular/router": "^15.0.0", + "@ng-matero/extensions": "^15.0.1", + "@ng-matero/extensions-moment-adapter": "^15.0.0", + "@ngx-formly/core": "^6.0.4", + "@ngx-formly/material": "^6.0.4", + "@ngx-translate/core": "^14.0.0", + "@ngx-translate/http-loader": "^7.0.0", + "moment": "^2.29.4", + "ng-matero": "^15.1.1", + "ngx-permissions": "^14.0.0", + "ngx-progressbar": "^9.0.0", + "ngx-toastr": "^16.0.1", + "photoviewer": "^3.6.6", "rxjs": "~7.5.0", + "screenfull": "^6.0.2", "tslib": "^2.3.0", "zone.js": "~0.12.0" }, "devDependencies": { "@angular-devkit/build-angular": "^15.0.5", + "@angular-eslint/builder": "^15.1.0", + "@angular-eslint/eslint-plugin": "^15.1.0", + "@angular-eslint/eslint-plugin-template": "^15.1.0", + "@angular-eslint/schematics": "^15.1.0", + "@angular-eslint/template-parser": "^15.1.0", "@angular/cli": "~15.0.5", "@angular/compiler-cli": "^15.0.0", "@types/jasmine": "~4.3.0", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", + "eslint": "^8.30.0", "jasmine-core": "~4.5.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "parse5": "^7.1.2", + "prettier": "^2.8.1", + "stylelint": "^14.16.0", + "stylelint-config-rational-order": "^0.1.2", + "stylelint-config-recommended-scss": "^8.0.0", + "stylelint-config-standard": "^29.0.0", + "stylelint-order": "^5.0.0", + "stylelint-scss": "^4.3.0", "typescript": "~4.8.2" } } \ No newline at end of file diff --git a/front/app/proxy.config.js b/front/app/proxy.config.js new file mode 100644 index 00000000..3e1f7cf1 --- /dev/null +++ b/front/app/proxy.config.js @@ -0,0 +1,22 @@ +// https://angular.io/guide/build#proxying-to-a-backend-server + +const PROXY_CONFIG = { + '/users/**': { + target: 'https://api.github.com', + changeOrigin: true, + secure: false, + logLevel: 'debug', + // onProxyReq: (proxyReq, req, res) => { + // const cookieMap = { + // SID: '', + // }; + // let cookie = ''; + // for (const key of Object.keys(cookieMap)) { + // cookie += `${key}=${cookieMap[key]}; `; + // } + // proxyReq.setHeader('cookie', cookie); + // }, + }, +}; + +module.exports = PROXY_CONFIG; diff --git a/front/app/src/app/app-routing.module.ts b/front/app/src/app/app-routing.module.ts deleted file mode 100644 index 02972627..00000000 --- a/front/app/src/app/app-routing.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; - -const routes: Routes = []; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { } diff --git a/front/app/src/app/app.component.html b/front/app/src/app/app.component.html deleted file mode 100644 index e32be577..00000000 --- a/front/app/src/app/app.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/front/app/src/app/app.component.spec.ts b/front/app/src/app/app.component.spec.ts deleted file mode 100644 index d5efb8d5..00000000 --- a/front/app/src/app/app.component.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'app'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('app'); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('.content span')?.textContent).toContain('app app is running!'); - }); -}); diff --git a/front/app/src/app/app.component.ts b/front/app/src/app/app.component.ts index 7b0f6728..01bb15da 100644 --- a/front/app/src/app/app.component.ts +++ b/front/app/src/app/app.component.ts @@ -1,10 +1,16 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, AfterViewInit } from '@angular/core'; +import { PreloaderService } from '@core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + template: '', }) -export class AppComponent { - title = 'app'; +export class AppComponent implements OnInit, AfterViewInit { + constructor(private preloader: PreloaderService) {} + + ngOnInit() {} + + ngAfterViewInit() { + this.preloader.hide(); + } } diff --git a/front/app/src/app/app.module.ts b/front/app/src/app/app.module.ts index d03631cb..c0bedb1e 100644 --- a/front/app/src/app/app.module.ts +++ b/front/app/src/app/app.module.ts @@ -1,23 +1,58 @@ import { NgModule } from '@angular/core'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; import { BrowserModule } from '@angular/platform-browser'; -import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { AuthComponent } from './auth/auth.component'; + +import { CoreModule } from '@core/core.module'; +import { ThemeModule } from '@theme/theme.module'; +import { SharedModule } from '@shared/shared.module'; +import { RoutesModule } from './routes/routes.module'; +import { FormlyConfigModule } from './formly-config.module'; +import { NgxPermissionsModule } from 'ngx-permissions'; +import { ToastrModule } from 'ngx-toastr'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +import { environment } from '@env/environment'; +import { BASE_URL, httpInterceptorProviders, appInitializerProviders } from '@core'; + +// Required for AOT compilation +export function TranslateHttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} + +import { LoginService } from '@core/authentication/login.service'; +import { FakeLoginService } from './fake-login.service'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @NgModule({ - declarations: [ - AppComponent, - AuthComponent - - ], + declarations: [AppComponent], imports: [ BrowserModule, - AppRoutingModule, - BrowserAnimationsModule + HttpClientModule, + CoreModule, + ThemeModule, + RoutesModule, + SharedModule, + FormlyConfigModule.forRoot(), + NgxPermissionsModule.forRoot(), + ToastrModule.forRoot(), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: TranslateHttpLoaderFactory, + deps: [HttpClient], + }, + }), + BrowserAnimationsModule, ], - providers: [], - bootstrap: [AppComponent] + providers: [ + { provide: BASE_URL, useValue: environment.baseUrl }, + { provide: LoginService, useClass: FakeLoginService }, // <= Remove it in the real APP + httpInterceptorProviders, + appInitializerProviders, + ], + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/front/app/src/app/auth/auth.component.html b/front/app/src/app/auth/auth.component.html deleted file mode 100644 index f66eb695..00000000 --- a/front/app/src/app/auth/auth.component.html +++ /dev/null @@ -1 +0,0 @@ -

auth works!

diff --git a/front/app/src/app/auth/auth.component.spec.ts b/front/app/src/app/auth/auth.component.spec.ts deleted file mode 100644 index ce785350..00000000 --- a/front/app/src/app/auth/auth.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AuthComponent } from './auth.component'; - -describe('AuthComponent', () => { - let component: AuthComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ AuthComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AuthComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/front/app/src/app/auth/auth.component.ts b/front/app/src/app/auth/auth.component.ts deleted file mode 100644 index c45c6b7f..00000000 --- a/front/app/src/app/auth/auth.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'app-auth', - templateUrl: './auth.component.html', - styleUrls: ['./auth.component.css'] -}) -export class AuthComponent { - -} diff --git a/front/app/src/app/core/authentication/README.md b/front/app/src/app/core/authentication/README.md new file mode 100644 index 00000000..2517328e --- /dev/null +++ b/front/app/src/app/core/authentication/README.md @@ -0,0 +1,17 @@ +# Authentication + +1. Modify the token key at `token.service` to another name such as `TOKEN` or `your-app-token`. By default set to `ng-matero-token`. + +2. Replace the APIs at `login.service` with your owns. + + - `/auth/login` Login + - `/auth/refresh` Refresh + - `/auth/logout` Logout + - `/me` Get user information + - `/me/menu` Get user menu + +3. If you have modified the login url (defaults to `auth/login`), you should correct it in the following files. + + - `auth.guard.ts` + - `error-interceptor.ts` + - `token-interceptor.ts` diff --git a/front/app/src/app/core/authentication/auth.guard.spec.ts b/front/app/src/app/core/authentication/auth.guard.spec.ts new file mode 100644 index 00000000..d068838c --- /dev/null +++ b/front/app/src/app/core/authentication/auth.guard.spec.ts @@ -0,0 +1,56 @@ +import { TestBed } from '@angular/core/testing'; + +import { Router } from '@angular/router'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Component } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { TokenService, AuthGuard, AuthService } from '@core/authentication'; + +@Component({ template: '' }) +class DummyComponent {} + +describe('AuthGuard', () => { + const route: any = {}; + const state: any = {}; + let router: Router; + let authGuard: AuthGuard; + let authService: AuthService; + let tokenService: TokenService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + RouterTestingModule.withRoutes([ + { path: 'dashboard', component: DummyComponent, canActivate: [AuthGuard] }, + { path: 'auth/login', component: DummyComponent }, + ]), + ], + declarations: [DummyComponent], + providers: [{ provide: LocalStorageService, useClass: MemoryStorageService }], + }); + TestBed.createComponent(DummyComponent); + + router = TestBed.inject(Router); + authGuard = TestBed.inject(AuthGuard); + authService = TestBed.inject(AuthService); + tokenService = TestBed.inject(TokenService); + }); + + it('should be created', () => { + expect(authGuard).toBeTruthy(); + }); + + it('should be authenticated', () => { + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + expect(authGuard.canActivate(route, state)).toBeTrue(); + }); + + it('should redirect to /auth/login when authenticate failed', () => { + spyOn(authService, 'check').and.returnValue(false); + + expect(authGuard.canActivate(route, state)).toEqual(router.parseUrl('/auth/login')); + }); +}); diff --git a/front/app/src/app/core/authentication/auth.guard.ts b/front/app/src/app/core/authentication/auth.guard.ts new file mode 100644 index 00000000..491dd640 --- /dev/null +++ b/front/app/src/app/core/authentication/auth.guard.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivate, + CanActivateChild, + Router, + RouterStateSnapshot, + UrlTree, +} from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthGuard implements CanActivate, CanActivateChild { + constructor(private auth: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + return this.authenticate(); + } + + canActivateChild( + childRoute: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean | UrlTree { + return this.authenticate(); + } + + private authenticate(): boolean | UrlTree { + return this.auth.check() ? true : this.router.parseUrl('/auth/login'); + } +} diff --git a/front/app/src/app/core/authentication/auth.service.spec.ts b/front/app/src/app/core/authentication/auth.service.spec.ts new file mode 100644 index 00000000..81df5a01 --- /dev/null +++ b/front/app/src/app/core/authentication/auth.service.spec.ts @@ -0,0 +1,133 @@ +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { Observable } from 'rxjs'; +import { skip } from 'rxjs/operators'; +import { HttpRequest } from '@angular/common/http'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { AuthService, LoginService, TokenService, User } from '@core/authentication'; + +describe('AuthService', () => { + let authService: AuthService; + let loginService: LoginService; + let tokenService: TokenService; + let httpMock: HttpTestingController; + let user$: Observable; + const email = 'foo@bar.com'; + const token = { access_token: 'token', token_type: 'bearer' }; + const user = { id: 1, email }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [{ provide: LocalStorageService, useClass: MemoryStorageService }], + }); + loginService = TestBed.inject(LoginService); + authService = TestBed.inject(AuthService); + tokenService = TestBed.inject(TokenService); + httpMock = TestBed.inject(HttpTestingController); + + user$ = authService.user(); + authService.change().subscribe(user => { + expect(user).toBeInstanceOf(Object); + }); + }); + + afterEach(() => httpMock.verify()); + + it('should be created', () => { + expect(authService).toBeTruthy(); + }); + + it('should log in failed', () => { + authService.login(email, 'password', false).subscribe(isLogin => expect(isLogin).toBeFalse()); + httpMock.expectOne('/auth/login').flush({}); + + expect(authService.check()).toBeFalse(); + }); + + it('should log in successful and get user info', () => { + user$.pipe(skip(1)).subscribe(currentUser => expect(currentUser.id).toEqual(user.id)); + authService.login(email, 'password', false).subscribe(isLogin => expect(isLogin).toBeTrue()); + httpMock.expectOne('/auth/login').flush(token); + + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + }); + + it('should log out failed when user is not login', () => { + spyOn(loginService, 'logout').and.callThrough(); + expect(authService.check()).toBeFalse(); + + authService.logout().subscribe(); + httpMock.expectOne('/auth/logout'); + + expect(authService.check()).toBeFalse(); + expect(loginService.logout).toHaveBeenCalled(); + }); + + it('should log out successful when user is login', () => { + tokenService.set(token); + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + + user$.pipe(skip(1)).subscribe(currentUser => expect(currentUser.id).toBeUndefined()); + authService.logout().subscribe(); + httpMock.expectOne('/auth/logout').flush({}); + + expect(authService.check()).toBeFalse(); + }); + + it('should refresh token when access_token is valid', fakeAsync(() => { + tokenService.set(Object.assign({ expires_in: 5 }, token)); + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + const match = (req: HttpRequest) => req.url === '/auth/refresh' && !req.body.refresh_token; + + tick(4000); + expect(authService.check()).toBeTrue(); + httpMock.match(match)[0].flush(token); + + expect(authService.check()).toBeTrue(); + httpMock.expectNone('/me'); + tokenService.ngOnDestroy(); + })); + + it('should refresh token when access_token is invalid and refresh_token is valid', fakeAsync(() => { + tokenService.set(Object.assign({ expires_in: 5, refresh_token: 'foo' }, token)); + const match = (req: HttpRequest) => + req.url === '/auth/refresh' && req.body.refresh_token === 'foo'; + + expect(authService.check()).toBeTrue(); + httpMock.expectOne('/me').flush(user); + tick(10000); + expect(authService.check()).toBeFalse(); + httpMock.match(match)[0].flush(token); + + expect(authService.check()).toBeTrue(); + httpMock.expectNone('/me'); + tokenService.ngOnDestroy(); + })); + + it('it should clear token when access_token is invalid and refresh token response is 401', fakeAsync(() => { + spyOn(tokenService, 'set').and.callThrough(); + tokenService.set(Object.assign({ expires_in: 5, refresh_token: 'foo' }, token)); + const match = (req: HttpRequest) => + req.url === '/auth/refresh' && req.body.refresh_token === 'foo'; + + tick(10000); + expect(authService.check()).toBeFalse(); + httpMock.expectOne('/me').flush({}); + httpMock.match(match)[0].flush({}, { status: 401, statusText: 'Unauthorized' }); + + expect(authService.check()).toBeFalse(); + expect(tokenService.set).toHaveBeenCalledWith(undefined); + tokenService.ngOnDestroy(); + })); + + it('it only call http request once when on change subscribe twice', () => { + authService.change().subscribe(); + tokenService.set(token); + httpMock.expectOne('/me').flush({}); + }); +}); diff --git a/front/app/src/app/core/authentication/auth.service.ts b/front/app/src/app/core/authentication/auth.service.ts new file mode 100644 index 00000000..1769588d --- /dev/null +++ b/front/app/src/app/core/authentication/auth.service.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, iif, merge, of } from 'rxjs'; +import { catchError, map, share, switchMap, tap } from 'rxjs/operators'; +import { TokenService } from './token.service'; +import { LoginService } from './login.service'; +import { filterObject, isEmptyObject } from './helpers'; +import { User } from './interface'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private user$ = new BehaviorSubject({}); + private change$ = merge( + this.tokenService.change(), + this.tokenService.refresh().pipe(switchMap(() => this.refresh())) + ).pipe( + switchMap(() => this.assignUser()), + share() + ); + + constructor(private loginService: LoginService, private tokenService: TokenService) {} + + init() { + return new Promise(resolve => this.change$.subscribe(() => resolve())); + } + + change() { + return this.change$; + } + + check() { + return this.tokenService.valid(); + } + + login(username: string, password: string, rememberMe = false) { + return this.loginService.login(username, password, rememberMe).pipe( + tap(token => this.tokenService.set(token)), + map(() => this.check()) + ); + } + + refresh() { + return this.loginService + .refresh(filterObject({ refresh_token: this.tokenService.getRefreshToken() })) + .pipe( + catchError(() => of(undefined)), + tap(token => this.tokenService.set(token)), + map(() => this.check()) + ); + } + + logout() { + return this.loginService.logout().pipe( + tap(() => this.tokenService.clear()), + map(() => !this.check()) + ); + } + + user() { + return this.user$.pipe(share()); + } + + menu() { + return iif(() => this.check(), this.loginService.menu(), of([])); + } + + private assignUser() { + if (!this.check()) { + return of({}).pipe(tap(user => this.user$.next(user))); + } + + if (!isEmptyObject(this.user$.getValue())) { + return of(this.user$.getValue()); + } + + return this.loginService.me().pipe(tap(user => this.user$.next(user))); + } +} diff --git a/front/app/src/app/core/authentication/helpers.ts b/front/app/src/app/core/authentication/helpers.ts new file mode 100644 index 00000000..a6f2aeff --- /dev/null +++ b/front/app/src/app/core/authentication/helpers.ts @@ -0,0 +1,57 @@ +import { fromByteArray, toByteArray } from 'base64-js'; + +export class Base64 { + static encode(plainText: string): string { + return fromByteArray(pack(plainText)).replace(/[+/=]/g, m => { + return { '+': '-', '/': '_', '=': '' }[m] as string; + }); + } + + static decode(b64: string): string { + b64 = b64.replace(/[-_]/g, m => { + return { '-': '+', '_': '/' }[m] as string; + }); + while (b64.length % 4) { + b64 += '='; + } + + return unpack(toByteArray(b64)); + } +} + +export function pack(str: string) { + const bytes: any = []; + for (let i = 0; i < str.length; i++) { + bytes.push(...[str.charCodeAt(i)]); + } + + return bytes; +} + +export function unpack(byteArray: any) { + return String.fromCharCode(...byteArray); +} + +export const base64 = { encode: Base64.encode, decode: Base64.decode }; + +export function capitalize(text: string): string { + return text.substring(0, 1).toUpperCase() + text.substring(1, text.length).toLowerCase(); +} + +export function currentTimestamp(): number { + return Math.ceil(new Date().getTime() / 1000); +} + +export function timeLeft(expiredAt: number): number { + return Math.max(0, expiredAt - currentTimestamp()); +} + +export function filterObject>(obj: T) { + return Object.fromEntries( + Object.entries(obj).filter(([, value]) => value !== undefined && value !== null) + ); +} + +export function isEmptyObject(obj: Record) { + return Object.keys(obj).length === 0; +} diff --git a/front/app/src/app/core/authentication/index.ts b/front/app/src/app/core/authentication/index.ts new file mode 100644 index 00000000..e3bb2056 --- /dev/null +++ b/front/app/src/app/core/authentication/index.ts @@ -0,0 +1,9 @@ +export * from './interface'; +export * from './auth.guard'; +export * from './auth.service'; +export * from './token-factory.service'; +export * from './token.service'; +export * from './token'; +export * from './login.service'; +export * from './user'; +export * from './helpers'; diff --git a/front/app/src/app/core/authentication/interface.ts b/front/app/src/app/core/authentication/interface.ts new file mode 100644 index 00000000..ce50657a --- /dev/null +++ b/front/app/src/app/core/authentication/interface.ts @@ -0,0 +1,20 @@ +export interface User { + [prop: string]: any; + + id?: number | string | null; + name?: string; + email?: string; + avatar?: string; + roles?: any[]; + permissions?: any[]; +} + +export interface Token { + [prop: string]: any; + + access_token: string; + token_type?: string; + expires_in?: number; + exp?: number; + refresh_token?: string; +} diff --git a/front/app/src/app/core/authentication/login.service.ts b/front/app/src/app/core/authentication/login.service.ts new file mode 100644 index 00000000..4d3a7658 --- /dev/null +++ b/front/app/src/app/core/authentication/login.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Token, User } from './interface'; +import { Menu } from '@core'; +import { map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class LoginService { + constructor(protected http: HttpClient) {} + + login(username: string, password: string, rememberMe = false) { + return this.http.post('/auth/login', { username, password, rememberMe }); + } + + refresh(params: Record) { + return this.http.post('/auth/refresh', params); + } + + logout() { + return this.http.post('/auth/logout', {}); + } + + me() { + return this.http.get('/me'); + } + + menu() { + return this.http.get<{ menu: Menu[] }>('/me/menu').pipe(map(res => res.menu)); + } +} diff --git a/front/app/src/app/core/authentication/token-factory.service.ts b/front/app/src/app/core/authentication/token-factory.service.ts new file mode 100644 index 00000000..99203297 --- /dev/null +++ b/front/app/src/app/core/authentication/token-factory.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Token } from './interface'; +import { SimpleToken, JwtToken, BaseToken } from './token'; + +@Injectable({ + providedIn: 'root', +}) +export class TokenFactory { + create(attributes: Token): BaseToken | undefined { + if (!attributes.access_token) { + return undefined; + } + + if (JwtToken.is(attributes.access_token)) { + return new JwtToken(attributes); + } + + return new SimpleToken(attributes); + } +} diff --git a/front/app/src/app/core/authentication/token.service.spec.ts b/front/app/src/app/core/authentication/token.service.spec.ts new file mode 100644 index 00000000..2e424e5a --- /dev/null +++ b/front/app/src/app/core/authentication/token.service.spec.ts @@ -0,0 +1,52 @@ +import { TestBed } from '@angular/core/testing'; +import { tap } from 'rxjs/operators'; +import { MemoryStorageService, LocalStorageService } from '@shared/services/storage.service'; +import { TokenService, currentTimestamp, TokenFactory, SimpleToken } from '@core/authentication'; + +describe('TokenService', () => { + let tokenService: TokenService; + let tokenFactory: TokenFactory; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [{ provide: LocalStorageService, useClass: MemoryStorageService }], + }); + tokenService = TestBed.inject(TokenService); + tokenFactory = TestBed.inject(TokenFactory); + }); + + it('should be created', () => { + expect(tokenService).toBeTruthy(); + }); + + it('should get authorization header value', () => { + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + expect(tokenService.getBearerToken()).toEqual('Bearer token'); + }); + + it('cannot get authorization header value', () => { + tokenService.set({ access_token: '', token_type: 'bearer' }); + + expect(tokenService.getBearerToken()).toBe(''); + }); + + it('should not has exp when token has expires_in', () => { + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + tokenService + .change() + .pipe(tap(token => expect(token!.exp).toBeUndefined())) + .subscribe(); + }); + + it('should has exp when token has expires_in', () => { + const expiresIn = 3600; + tokenService.set({ access_token: 'token', token_type: 'bearer', expires_in: expiresIn }); + + tokenService + .change() + .pipe(tap(token => expect(token!.exp).toEqual(currentTimestamp() + expiresIn))) + .subscribe(); + }); +}); diff --git a/front/app/src/app/core/authentication/token.service.ts b/front/app/src/app/core/authentication/token.service.ts new file mode 100644 index 00000000..c78bb3d8 --- /dev/null +++ b/front/app/src/app/core/authentication/token.service.ts @@ -0,0 +1,99 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { BehaviorSubject, Observable, Subject, Subscription, timer } from 'rxjs'; +import { share } from 'rxjs/operators'; +import { LocalStorageService } from '@shared'; +import { Token } from './interface'; +import { BaseToken } from './token'; +import { TokenFactory } from './token-factory.service'; +import { currentTimestamp, filterObject } from './helpers'; + +@Injectable({ + providedIn: 'root', +}) +export class TokenService implements OnDestroy { + private key = 'ng-matero-token'; + + private change$ = new BehaviorSubject(undefined); + private refresh$ = new Subject(); + private timer$?: Subscription; + + private _token?: BaseToken; + + constructor(private store: LocalStorageService, private factory: TokenFactory) {} + + private get token(): BaseToken | undefined { + if (!this._token) { + this._token = this.factory.create(this.store.get(this.key)); + } + + return this._token; + } + + change(): Observable { + return this.change$.pipe(share()); + } + + refresh(): Observable { + this.buildRefresh(); + + return this.refresh$.pipe(share()); + } + + set(token?: Token): TokenService { + this.save(token); + + return this; + } + + clear(): void { + this.save(); + } + + valid(): boolean { + return this.token?.valid() ?? false; + } + + getBearerToken(): string { + return this.token?.getBearerToken() ?? ''; + } + + getRefreshToken(): string | void { + return this.token?.refresh_token; + } + + ngOnDestroy(): void { + this.clearRefresh(); + } + + private save(token?: Token): void { + this._token = undefined; + + if (!token) { + this.store.remove(this.key); + } else { + const value = Object.assign({ access_token: '', token_type: 'Bearer' }, token, { + exp: token.expires_in ? currentTimestamp() + token.expires_in : null, + }); + this.store.set(this.key, filterObject(value)); + } + + this.change$.next(this.token); + this.buildRefresh(); + } + + private buildRefresh() { + this.clearRefresh(); + + if (this.token?.needRefresh()) { + this.timer$ = timer(this.token.getRefreshTime() * 1000).subscribe(() => { + this.refresh$.next(this.token); + }); + } + } + + private clearRefresh() { + if (this.timer$ && !this.timer$.closed) { + this.timer$.unsubscribe(); + } + } +} diff --git a/front/app/src/app/core/authentication/token.spec.ts b/front/app/src/app/core/authentication/token.spec.ts new file mode 100644 index 00000000..2918699c --- /dev/null +++ b/front/app/src/app/core/authentication/token.spec.ts @@ -0,0 +1,41 @@ +import { base64, currentTimestamp, JwtToken } from '@core/authentication'; + +describe('Token', () => { + describe('JwtToken', () => { + function generateToken(params: any, typ = 'JWT') { + return [ + base64.encode(JSON.stringify({ typ, alg: 'HS256' })), + base64.encode(JSON.stringify(params)), + base64.encode('ng-matero'), + ].join('.'); + } + + const exp = currentTimestamp() + 3600; + const token = new JwtToken({ + access_token: generateToken({ exp }, 'at+JWT'), + token_type: 'Bearer', + }); + + it('test access_token is JWT', () => { + expect(JwtToken.is(token.access_token)).toBeTrue(); + }); + + it('test bearer token', function () { + expect(token.getBearerToken()).toBe(`Bearer ${token.access_token}`); + }); + + it('test payload has exp attribute', () => { + expect(token.exp).toEqual(exp); + }); + + it('test payload does not has exp attribute', () => { + expect(token.exp).toEqual(exp); + }); + + it('test does not has exp attribute', () => { + const token = new JwtToken({ access_token: generateToken({}), token_type: 'Bearer' }); + + expect(token.exp).toBeUndefined(); + }); + }); +}); diff --git a/front/app/src/app/core/authentication/token.ts b/front/app/src/app/core/authentication/token.ts new file mode 100644 index 00000000..f622cf89 --- /dev/null +++ b/front/app/src/app/core/authentication/token.ts @@ -0,0 +1,87 @@ +import { base64, capitalize, currentTimestamp, timeLeft } from './helpers'; +import { Token } from './interface'; + +export abstract class BaseToken { + constructor(protected attributes: Token) {} + + get access_token(): string { + return this.attributes.access_token; + } + + get refresh_token(): string | void { + return this.attributes.refresh_token; + } + + get token_type(): string { + return this.attributes.token_type ?? 'bearer'; + } + + get exp(): number | void { + return this.attributes.exp; + } + + valid(): boolean { + return this.hasAccessToken() && !this.isExpired(); + } + + getBearerToken(): string { + return this.access_token + ? [capitalize(this.token_type), this.access_token].join(' ').trim() + : ''; + } + + needRefresh(): boolean { + return this.exp !== undefined && this.exp >= 0; + } + + getRefreshTime(): number { + return timeLeft((this.exp ?? 0) - 5); + } + + private hasAccessToken(): boolean { + return !!this.access_token; + } + + private isExpired(): boolean { + return this.exp !== undefined && this.exp - currentTimestamp() <= 0; + } +} + +export class SimpleToken extends BaseToken {} + +export class JwtToken extends SimpleToken { + private _payload?: { exp?: number | void }; + + static is(accessToken: string): boolean { + try { + const [_header] = accessToken.split('.'); + const header = JSON.parse(base64.decode(_header)); + + return header.typ.toUpperCase().includes('JWT'); + } catch (e) { + return false; + } + } + + get exp(): number | void { + return this.payload?.exp; + } + + private get payload(): { exp?: number | void } { + if (!this.access_token) { + return {}; + } + + if (this._payload) { + return this._payload; + } + + const [, payload] = this.access_token.split('.'); + const data = JSON.parse(base64.decode(payload)); + if (!data.exp) { + data.exp = this.attributes.exp; + } + + return (this._payload = data); + } +} diff --git a/front/app/src/app/core/authentication/user.ts b/front/app/src/app/core/authentication/user.ts new file mode 100644 index 00000000..332504aa --- /dev/null +++ b/front/app/src/app/core/authentication/user.ts @@ -0,0 +1,14 @@ +import { User } from './interface'; + +export const admin: User = { + id: 1, + name: 'Zongbin', + email: 'nzb329@163.com', + avatar: './assets/images/avatar.jpg', +}; + +export const guest: User = { + name: 'unknown', + email: 'unknown', + avatar: './assets/images/avatar-default.jpg', +}; diff --git a/front/app/src/app/core/bootstrap/README.md b/front/app/src/app/core/bootstrap/README.md new file mode 100644 index 00000000..c769e407 --- /dev/null +++ b/front/app/src/app/core/bootstrap/README.md @@ -0,0 +1,3 @@ +# Bootstrap + +The services in this folder should be singletons and used for sharing data and functionality. diff --git a/front/app/src/app/core/bootstrap/menu.service.ts b/front/app/src/app/core/bootstrap/menu.service.ts new file mode 100644 index 00000000..148caf23 --- /dev/null +++ b/front/app/src/app/core/bootstrap/menu.service.ts @@ -0,0 +1,150 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { share } from 'rxjs/operators'; + +export interface MenuTag { + color: string; // background color + value: string; +} + +export interface MenuPermissions { + only?: string | string[]; + except?: string | string[]; +} + +export interface MenuChildrenItem { + route: string; + name: string; + type: 'link' | 'sub' | 'extLink' | 'extTabLink'; + children?: MenuChildrenItem[]; + permissions?: MenuPermissions; +} + +export interface Menu { + route: string; + name: string; + type: 'link' | 'sub' | 'extLink' | 'extTabLink'; + icon: string; + label?: MenuTag; + badge?: MenuTag; + children?: MenuChildrenItem[]; + permissions?: MenuPermissions; +} + +@Injectable({ + providedIn: 'root', +}) +export class MenuService { + private menu$: BehaviorSubject = new BehaviorSubject([]); + + /** Get all the menu data. */ + getAll(): Observable { + return this.menu$.asObservable(); + } + + /** Observe the change of menu data. */ + change(): Observable { + return this.menu$.pipe(share()); + } + + /** Initialize the menu data. */ + set(menu: Menu[]): Observable { + this.menu$.next(menu); + return this.menu$.asObservable(); + } + + /** Add one item to the menu data. */ + add(menu: Menu) { + const tmpMenu = this.menu$.value; + tmpMenu.push(menu); + this.menu$.next(tmpMenu); + } + + /** Reset the menu data. */ + reset() { + this.menu$.next([]); + } + + /** Delete empty values and rebuild route. */ + buildRoute(routeArr: string[]): string { + let route = ''; + routeArr.forEach(item => { + if (item && item.trim()) { + route += '/' + item.replace(/^\/+|\/+$/g, ''); + } + }); + return route; + } + + /** Get the menu item name based on current route. */ + getItemName(routeArr: string[]): string { + return this.getLevel(routeArr)[routeArr.length - 1]; + } + + // Whether is a leaf menu + private isLeafItem(item: MenuChildrenItem): boolean { + const cond0 = item.route === undefined; + const cond1 = item.children === undefined; + const cond2 = !cond1 && item.children?.length === 0; + return cond0 || cond1 || cond2; + } + + // Deep clone object could be jsonized + private deepClone(obj: any): any { + return JSON.parse(JSON.stringify(obj)); + } + + // Whether two objects could be jsonized equal + private isJsonObjEqual(obj0: any, obj1: any): boolean { + return JSON.stringify(obj0) === JSON.stringify(obj1); + } + + // Whether routeArr equals realRouteArr (after remove empty route element) + private isRouteEqual(routeArr: Array, realRouteArr: Array): boolean { + realRouteArr = this.deepClone(realRouteArr); + realRouteArr = realRouteArr.filter(r => r !== ''); + return this.isJsonObjEqual(routeArr, realRouteArr); + } + + /** Get the menu level. */ + getLevel(routeArr: string[]): string[] { + let tmpArr: any[] = []; + this.menu$.value.forEach(item => { + // Breadth-first traverse + let unhandledLayer = [{ item, parentNamePathList: [], realRouteArr: [] }]; + while (unhandledLayer.length > 0) { + let nextUnhandledLayer: any[] = []; + for (const ele of unhandledLayer) { + const eachItem = ele.item; + const currentNamePathList = this.deepClone(ele.parentNamePathList).concat(eachItem.name); + const currentRealRouteArr = this.deepClone(ele.realRouteArr).concat(eachItem.route); + // Compare the full Array for expandable + if (this.isRouteEqual(routeArr, currentRealRouteArr)) { + tmpArr = currentNamePathList; + break; + } + if (!this.isLeafItem(eachItem)) { + const wrappedChildren = eachItem.children?.map(child => ({ + item: child, + parentNamePathList: currentNamePathList, + realRouteArr: currentRealRouteArr, + })); + nextUnhandledLayer = nextUnhandledLayer.concat(wrappedChildren); + } + } + unhandledLayer = nextUnhandledLayer; + } + }); + return tmpArr; + } + + /** Add namespace for translation. */ + addNamespace(menu: Menu[] | MenuChildrenItem[], namespace: string) { + menu.forEach(menuItem => { + menuItem.name = `${namespace}.${menuItem.name}`; + if (menuItem.children && menuItem.children.length > 0) { + this.addNamespace(menuItem.children, menuItem.name); + } + }); + } +} diff --git a/front/app/src/app/core/bootstrap/preloader.service.ts b/front/app/src/app/core/bootstrap/preloader.service.ts new file mode 100644 index 00000000..d19e8823 --- /dev/null +++ b/front/app/src/app/core/bootstrap/preloader.service.ts @@ -0,0 +1,28 @@ +import { Inject, Injectable } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; + +@Injectable({ + providedIn: 'root', +}) +export class PreloaderService { + private selector = 'globalLoader'; + + constructor(@Inject(DOCUMENT) private document: Document) {} + + private getElement() { + return this.document.getElementById(this.selector); + } + + hide() { + const el = this.getElement(); + if (el) { + el.addEventListener('transitionend', () => { + el.className = 'global-loader-hidden'; + }); + + if (!el.classList.contains('global-loader-hidden')) { + el.className += ' global-loader-fade-in'; + } + } + } +} diff --git a/front/app/src/app/core/bootstrap/sanctum.service.spec.ts b/front/app/src/app/core/bootstrap/sanctum.service.spec.ts new file mode 100644 index 00000000..30415504 --- /dev/null +++ b/front/app/src/app/core/bootstrap/sanctum.service.spec.ts @@ -0,0 +1,78 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClient } from '@angular/common/http'; +import { BASE_URL } from '../interceptors/base-url-interceptor'; +import { SANCTUM_PREFIX, SanctumService } from '@core'; + +describe('SanctumService', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let sanctumService: SanctumService; + + const setBaseUrlAndSanctumPrefix = (baseUrl: string | null, sanctumPrefix: string | null) => { + TestBed.overrideProvider(BASE_URL, { useValue: baseUrl }); + TestBed.overrideProvider(SANCTUM_PREFIX, { useValue: sanctumPrefix }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + sanctumService = TestBed.inject(SanctumService); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: BASE_URL, useValue: null }, + { provide: SANCTUM_PREFIX, useValue: null }, + SanctumService, + ], + }); + }); + + afterEach(() => httpMock.verify()); + + it('should get csrf cookie once', done => { + setBaseUrlAndSanctumPrefix(null, null); + + sanctumService.load().then(data => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('/sanctum/csrf-cookie').flush({ cookie: true }); + }); + + it('should get csrf cookie with base url', done => { + setBaseUrlAndSanctumPrefix('http://foo.bar/api', ''); + + sanctumService.load().then((data: any) => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('http://foo.bar/sanctum/csrf-cookie').flush({ cookie: true }); + }); + + it('should get csrf cookie with sanctum prefix', done => { + setBaseUrlAndSanctumPrefix(null, '/foobar/'); + + sanctumService.load().then((data: any) => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('/foobar/csrf-cookie').flush({ cookie: true }); + }); + + it('should get csrf cookie with base url and sanctum prefix', done => { + setBaseUrlAndSanctumPrefix('http://foo.bar/api/', '/foobar'); + + sanctumService.load().then((data: any) => { + expect(data).toEqual({ cookie: true }); + done(); + }); + + httpMock.expectOne('http://foo.bar/foobar/csrf-cookie').flush({ cookie: true }); + }); +}); diff --git a/front/app/src/app/core/bootstrap/sanctum.service.ts b/front/app/src/app/core/bootstrap/sanctum.service.ts new file mode 100644 index 00000000..24b50015 --- /dev/null +++ b/front/app/src/app/core/bootstrap/sanctum.service.ts @@ -0,0 +1,38 @@ +import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { BASE_URL } from '../interceptors/base-url-interceptor'; + +export const SANCTUM_PREFIX = new InjectionToken('SANCTUM_PREFIX'); + +@Injectable({ + providedIn: 'root', +}) +export class SanctumService { + constructor( + private http: HttpClient, + @Optional() @Inject(BASE_URL) private baseUrl?: string, + @Optional() @Inject(SANCTUM_PREFIX) private prefix?: string + ) {} + + load(): Promise { + return new Promise(resolve => this.toObservable().subscribe(resolve)); + } + + toObservable(): Observable { + return this.http.get(this.getUrl()); + } + + private getUrl(): string { + const prefix = this.prefix || 'sanctum'; + const path = `/${prefix.replace(/^\/|\/$/g, '')}/csrf-cookie`; + + if (!this.baseUrl) { + return path; + } + + const url = new URL(this.baseUrl); + + return url.origin + path; + } +} diff --git a/front/app/src/app/core/bootstrap/settings.service.ts b/front/app/src/app/core/bootstrap/settings.service.ts new file mode 100644 index 00000000..c377ba6a --- /dev/null +++ b/front/app/src/app/core/bootstrap/settings.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { AppSettings, defaults } from '../settings'; + +@Injectable({ + providedIn: 'root', +}) +export class SettingsService { + get notify(): Observable> { + return this.notify$.asObservable(); + } + + private notify$ = new BehaviorSubject>({}); + + getOptions() { + return this.options; + } + + setOptions(options: AppSettings) { + this.options = Object.assign(defaults, options); + this.notify$.next(this.options); + } + + private options = defaults; + + getLanguage() { + return this.options.language; + } + + setLanguage(lang: string) { + this.options.language = lang; + this.notify$.next({ lang }); + } +} diff --git a/front/app/src/app/core/bootstrap/startup.service.spec.ts b/front/app/src/app/core/bootstrap/startup.service.spec.ts new file mode 100644 index 00000000..d2c1149f --- /dev/null +++ b/front/app/src/app/core/bootstrap/startup.service.spec.ts @@ -0,0 +1,87 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { NgxPermissionsModule, NgxPermissionsService, NgxRolesService } from 'ngx-permissions'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { admin, TokenService } from '@core/authentication'; +import { MenuService } from '@core/bootstrap/menu.service'; +import { StartupService } from '@core/bootstrap/startup.service'; + +describe('StartupService', () => { + let httpMock: HttpTestingController; + let startup: StartupService; + let tokenService: TokenService; + let menuService: MenuService; + let mockPermissionsService: NgxPermissionsService; + let mockRolesService: NgxRolesService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, NgxPermissionsModule.forRoot()], + providers: [ + { + provide: LocalStorageService, + useClass: MemoryStorageService, + }, + { + provide: NgxPermissionsService, + useValue: { + loadPermissions: (permissions: string[]) => void 0, + }, + }, + { + provide: NgxRolesService, + useValue: { + flushRoles: () => void 0, + addRoles: (params: { ADMIN: string[] }) => void 0, + }, + }, + StartupService, + ], + }); + httpMock = TestBed.inject(HttpTestingController); + startup = TestBed.inject(StartupService); + tokenService = TestBed.inject(TokenService); + menuService = TestBed.inject(MenuService); + mockPermissionsService = TestBed.inject(NgxPermissionsService); + mockRolesService = TestBed.inject(NgxRolesService); + }); + + afterEach(() => httpMock.verify()); + + it('should load menu when token changed and token valid', async () => { + const menuData = { menu: [] }; + const permissions = ['canAdd', 'canDelete', 'canEdit', 'canRead']; + spyOn(menuService, 'addNamespace'); + spyOn(menuService, 'set'); + spyOn(mockPermissionsService, 'loadPermissions'); + spyOn(mockRolesService, 'flushRoles'); + spyOn(mockRolesService, 'addRoles'); + + await startup.load(); + + tokenService.set({ access_token: 'token', token_type: 'bearer' }); + + httpMock.expectOne('/me').flush(admin); + httpMock.expectOne('/me/menu').flush(menuData); + + expect(menuService.addNamespace).toHaveBeenCalledWith(menuData.menu, 'menu'); + expect(menuService.set).toHaveBeenCalledWith(menuData.menu); + expect(mockPermissionsService.loadPermissions).toHaveBeenCalledWith(permissions); + expect(mockRolesService.flushRoles).toHaveBeenCalledWith(); + expect(mockRolesService.addRoles).toHaveBeenCalledWith({ ADMIN: permissions }); + }); + + it('should clear menu when token changed and token invalid', async () => { + spyOn(menuService, 'addNamespace'); + spyOn(menuService, 'set'); + + await startup.load(); + + tokenService.set({ access_token: '', token_type: 'bearer' }); + + httpMock.expectNone('/me/menu'); + + expect(menuService.addNamespace).toHaveBeenCalledWith([], 'menu'); + expect(menuService.set).toHaveBeenCalledWith([]); + }); +}); diff --git a/front/app/src/app/core/bootstrap/startup.service.ts b/front/app/src/app/core/bootstrap/startup.service.ts new file mode 100644 index 00000000..8d94431e --- /dev/null +++ b/front/app/src/app/core/bootstrap/startup.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { switchMap, tap } from 'rxjs/operators'; +import { NgxPermissionsService, NgxRolesService } from 'ngx-permissions'; +import { AuthService, User } from '@core/authentication'; +import { Menu, MenuService } from './menu.service'; + +@Injectable({ + providedIn: 'root', +}) +export class StartupService { + constructor( + private authService: AuthService, + private menuService: MenuService, + private permissonsService: NgxPermissionsService, + private rolesService: NgxRolesService + ) {} + + /** + * Load the application only after get the menu or other essential informations + * such as permissions and roles. + */ + load() { + return new Promise((resolve, reject) => { + this.authService + .change() + .pipe( + tap(user => this.setPermissions(user)), + switchMap(() => this.authService.menu()), + tap(menu => this.setMenu(menu)) + ) + .subscribe( + () => resolve(), + () => resolve() + ); + }); + } + + private setMenu(menu: Menu[]) { + this.menuService.addNamespace(menu, 'menu'); + this.menuService.set(menu); + } + + private setPermissions(user: User) { + // In a real app, you should get permissions and roles from the user information. + const permissions = ['canAdd', 'canDelete', 'canEdit', 'canRead']; + this.permissonsService.loadPermissions(permissions); + this.rolesService.flushRoles(); + this.rolesService.addRoles({ ADMIN: permissions }); + + // Tips: Alternatively you can add permissions with role at the same time. + // this.rolesService.addRolesWithPermissions({ ADMIN: permissions }); + } +} diff --git a/front/app/src/app/core/bootstrap/translate-lang.service.ts b/front/app/src/app/core/bootstrap/translate-lang.service.ts new file mode 100644 index 00000000..ec00790f --- /dev/null +++ b/front/app/src/app/core/bootstrap/translate-lang.service.ts @@ -0,0 +1,33 @@ +import { Injectable, Injector } from '@angular/core'; +import { LOCATION_INITIALIZED } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; +import { SettingsService } from './settings.service'; + +@Injectable({ + providedIn: 'root', +}) +export class TranslateLangService { + constructor( + private injector: Injector, + private translate: TranslateService, + private settings: SettingsService + ) {} + + load() { + return new Promise(resolve => { + const locationInitialized = this.injector.get(LOCATION_INITIALIZED, Promise.resolve()); + locationInitialized.then(() => { + const browserLang = navigator.language; + const defaultLang = browserLang.match(/en-US|zh-CN|zh-TW/) ? browserLang : 'en-US'; + + this.settings.setLanguage(defaultLang); + this.translate.setDefaultLang(defaultLang); + this.translate.use(defaultLang).subscribe( + () => console.log(`Successfully initialized '${defaultLang}' language.'`), + () => console.error(`Problem with '${defaultLang}' language initialization.'`), + () => resolve() + ); + }); + }); + } +} diff --git a/front/app/src/app/core/core.module.ts b/front/app/src/app/core/core.module.ts new file mode 100644 index 00000000..a8900f24 --- /dev/null +++ b/front/app/src/app/core/core.module.ts @@ -0,0 +1,13 @@ +import { NgModule, Optional, SkipSelf } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { throwIfAlreadyLoaded } from './module-import-guard'; + +@NgModule({ + declarations: [], + imports: [CommonModule], +}) +export class CoreModule { + constructor(@Optional() @SkipSelf() parentModule: CoreModule) { + throwIfAlreadyLoaded(parentModule, 'CoreModule'); + } +} diff --git a/front/app/src/app/core/index.ts b/front/app/src/app/core/index.ts new file mode 100644 index 00000000..acffc0f3 --- /dev/null +++ b/front/app/src/app/core/index.ts @@ -0,0 +1,16 @@ +export * from './settings'; +export * from './initializers'; + +// Bootstrap +export * from './bootstrap/menu.service'; +export * from './bootstrap/settings.service'; +export * from './bootstrap/startup.service'; +export * from './bootstrap/preloader.service'; +export * from './bootstrap/translate-lang.service'; +export * from './bootstrap/sanctum.service'; + +// Interceptors +export * from './interceptors'; + +// Authentication +export * from './authentication'; diff --git a/front/app/src/app/core/initializers.ts b/front/app/src/app/core/initializers.ts new file mode 100644 index 00000000..9fcd75ad --- /dev/null +++ b/front/app/src/app/core/initializers.ts @@ -0,0 +1,37 @@ +import { APP_INITIALIZER } from '@angular/core'; + +// import { SanctumService } from './bootstrap/sanctum.service'; +// export function SanctumServiceFactory(sanctumService: SanctumService) { +// return () => sanctumService.load(); +// } + +import { TranslateLangService } from './bootstrap/translate-lang.service'; +export function TranslateLangServiceFactory(translateLangService: TranslateLangService) { + return () => translateLangService.load(); +} + +import { StartupService } from './bootstrap/startup.service'; +export function StartupServiceFactory(startupService: StartupService) { + return () => startupService.load(); +} + +export const appInitializerProviders = [ + // { + // provide: APP_INITIALIZER, + // useFactory: SanctumServiceFactory, + // deps: [SanctumService], + // multi: true, + // }, + { + provide: APP_INITIALIZER, + useFactory: TranslateLangServiceFactory, + deps: [TranslateLangService], + multi: true, + }, + { + provide: APP_INITIALIZER, + useFactory: StartupServiceFactory, + deps: [StartupService], + multi: true, + }, +]; diff --git a/front/app/src/app/core/interceptors/README.md b/front/app/src/app/core/interceptors/README.md new file mode 100644 index 00000000..82e99862 --- /dev/null +++ b/front/app/src/app/core/interceptors/README.md @@ -0,0 +1,3 @@ +# Interceptors + +https://angular.io/guide/http#intercepting-requests-and-responses diff --git a/front/app/src/app/core/interceptors/base-url-interceptor.spec.ts b/front/app/src/app/core/interceptors/base-url-interceptor.spec.ts new file mode 100644 index 00000000..8509e256 --- /dev/null +++ b/front/app/src/app/core/interceptors/base-url-interceptor.spec.ts @@ -0,0 +1,47 @@ +import { TestBed } from '@angular/core/testing'; + +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { BASE_URL, BaseUrlInterceptor } from './base-url-interceptor'; + +describe('BaseUrlInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + const baseUrl = 'https://foo.bar'; + + const setBaseUrl = (url: string | null) => { + TestBed.overrideProvider(BASE_URL, { useValue: url }); + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: BASE_URL, useValue: null }, + { provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true }, + ], + }); + }); + + afterEach(() => httpMock.verify()); + + it('should not prepend base url when base url is empty', () => { + setBaseUrl(null); + + http.get('/me').subscribe(data => expect(data).toEqual({ success: true })); + + httpMock.expectOne('/me').flush({ success: true }); + }); + + it('should prepend base url when request url does not has http scheme', () => { + setBaseUrl(baseUrl); + + http.get('./me').subscribe(data => expect(data).toEqual({ success: true })); + httpMock.expectOne(baseUrl + '/me').flush({ success: true }); + + http.get('').subscribe(data => expect(data).toEqual({ success: true })); + httpMock.expectOne(baseUrl).flush({ success: true }); + }); +}); diff --git a/front/app/src/app/core/interceptors/base-url-interceptor.ts b/front/app/src/app/core/interceptors/base-url-interceptor.ts new file mode 100644 index 00000000..fc94c962 --- /dev/null +++ b/front/app/src/app/core/interceptors/base-url-interceptor.ts @@ -0,0 +1,24 @@ +import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export const BASE_URL = new InjectionToken('BASE_URL'); + +@Injectable() +export class BaseUrlInterceptor implements HttpInterceptor { + private hasScheme = (url: string) => this.baseUrl && new RegExp('^http(s)?://', 'i').test(url); + + constructor(@Optional() @Inject(BASE_URL) private baseUrl?: string) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return this.hasScheme(request.url) === false + ? next.handle(request.clone({ url: this.prependBaseUrl(request.url) })) + : next.handle(request); + } + + private prependBaseUrl(url: string) { + return [this.baseUrl?.replace(/\/$/g, ''), url.replace(/^\.?\//, '')] + .filter(val => val) + .join('/'); + } +} diff --git a/front/app/src/app/core/interceptors/default-interceptor.ts b/front/app/src/app/core/interceptors/default-interceptor.ts new file mode 100644 index 00000000..ad0238e4 --- /dev/null +++ b/front/app/src/app/core/interceptors/default-interceptor.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, +} from '@angular/common/http'; +import { Observable, of, throwError } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + +import { ToastrService } from 'ngx-toastr'; + +@Injectable() +export class DefaultInterceptor implements HttpInterceptor { + constructor(private toast: ToastrService) {} + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + if (!req.url.includes('/api/')) { + return next.handle(req); + } + + return next.handle(req).pipe(mergeMap((event: HttpEvent) => this.handleOkReq(event))); + } + + private handleOkReq(event: HttpEvent): Observable { + if (event instanceof HttpResponse) { + const body: any = event.body; + // failure: { code: **, msg: 'failure' } + // success: { code: 0, msg: 'success', data: {} } + if (body && 'code' in body && body.code !== 0) { + if (body.msg) { + this.toast.error(body.msg); + } + return throwError([]); + } + } + // Pass down event if everything is OK + return of(event); + } +} diff --git a/front/app/src/app/core/interceptors/error-interceptor.spec.ts b/front/app/src/app/core/interceptors/error-interceptor.spec.ts new file mode 100644 index 00000000..7f2a52a6 --- /dev/null +++ b/front/app/src/app/core/interceptors/error-interceptor.spec.ts @@ -0,0 +1,75 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Router } from '@angular/router'; +import { ToastrModule, ToastrService } from 'ngx-toastr'; +import { ErrorInterceptor } from './error-interceptor'; + +describe('ErrorInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let router: Router; + let toast: ToastrService; + const emptyFn = () => {}; + + function assertStatus(status: number, statusText: string) { + spyOn(router, 'navigateByUrl'); + + http.get('/me').subscribe(emptyFn, emptyFn, emptyFn); + + httpMock.expectOne('/me').flush({}, { status, statusText }); + + expect(router.navigateByUrl).toHaveBeenCalledWith(`/${status}`, { + skipLocationChange: true, + }); + } + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule, ToastrModule.forRoot()], + providers: [{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }], + }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + router = TestBed.inject(Router); + toast = TestBed.inject(ToastrService); + }); + + afterEach(() => httpMock.verify()); + + it('should handle status code 401', () => { + spyOn(router, 'navigateByUrl'); + spyOn(toast, 'error'); + + http.get('/me').subscribe(emptyFn, emptyFn, emptyFn); + httpMock.expectOne('/me').flush({}, { status: 401, statusText: 'Unauthorized' }); + + expect(toast.error).toHaveBeenCalledWith('401 Unauthorized'); + expect(router.navigateByUrl).toHaveBeenCalledWith('/auth/login'); + }); + + it('should handle status code 403', () => { + assertStatus(403, 'Forbidden'); + }); + + it('should handle status code 404', () => { + assertStatus(404, 'Not Found'); + }); + + it('should handle status code 500', () => { + assertStatus(500, 'Internal Server Error'); + }); + + it('should handle others status code', () => { + spyOn(toast, 'error'); + + http.get('/me').subscribe(emptyFn, emptyFn, emptyFn); + + httpMock.expectOne('/me').flush({}, { status: 504, statusText: 'Gateway Timeout' }); + + expect(toast.error).toHaveBeenCalledWith('504 Gateway Timeout'); + }); +}); diff --git a/front/app/src/app/core/interceptors/error-interceptor.ts b/front/app/src/app/core/interceptors/error-interceptor.ts new file mode 100644 index 00000000..d05531e1 --- /dev/null +++ b/front/app/src/app/core/interceptors/error-interceptor.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ToastrService } from 'ngx-toastr'; + +export enum STATUS { + UNAUTHORIZED = 401, + FORBIDDEN = 403, + NOT_FOUND = 404, + INTERNAL_SERVER_ERROR = 500, +} + +@Injectable() +export class ErrorInterceptor implements HttpInterceptor { + private errorPages = [STATUS.FORBIDDEN, STATUS.NOT_FOUND, STATUS.INTERNAL_SERVER_ERROR]; + + private getMessage = (error: HttpErrorResponse) => { + if (error.error?.message) { + return error.error.message; + } + + if (error.error?.msg) { + return error.error.msg; + } + + return `${error.status} ${error.statusText}`; + }; + + constructor(private router: Router, private toast: ToastrService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next + .handle(request) + .pipe(catchError((error: HttpErrorResponse) => this.handleError(error))); + } + + private handleError(error: HttpErrorResponse) { + if (this.errorPages.includes(error.status)) { + this.router.navigateByUrl(`/${error.status}`, { + skipLocationChange: true, + }); + } else { + console.error('ERROR', error); + this.toast.error(this.getMessage(error)); + if (error.status === STATUS.UNAUTHORIZED) { + this.router.navigateByUrl('/auth/login'); + } + } + + return throwError(error); + } +} diff --git a/front/app/src/app/core/interceptors/index.ts b/front/app/src/app/core/interceptors/index.ts new file mode 100644 index 00000000..7c098c5c --- /dev/null +++ b/front/app/src/app/core/interceptors/index.ts @@ -0,0 +1,31 @@ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { NoopInterceptor } from './noop-interceptor'; +// import { SanctumInterceptor } from './sanctum-interceptor'; +import { BaseUrlInterceptor } from './base-url-interceptor'; +import { SettingsInterceptor } from './settings-interceptor'; +import { TokenInterceptor } from './token-interceptor'; +import { DefaultInterceptor } from './default-interceptor'; +import { ErrorInterceptor } from './error-interceptor'; +import { LoggingInterceptor } from './logging-interceptor'; + +export * from './noop-interceptor'; +// export * from './sanctum-interceptor'; +export * from './base-url-interceptor'; +export * from './settings-interceptor'; +export * from './token-interceptor'; +export * from './default-interceptor'; +export * from './error-interceptor'; +export * from './logging-interceptor'; + +/** Http interceptor providers in outside-in order */ +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true }, + // { provide: HTTP_INTERCEPTORS, useClass: SanctumInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: SettingsInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }, +]; diff --git a/front/app/src/app/core/interceptors/logging-interceptor.ts b/front/app/src/app/core/interceptors/logging-interceptor.ts new file mode 100644 index 00000000..887c7ad1 --- /dev/null +++ b/front/app/src/app/core/interceptors/logging-interceptor.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http'; +import { finalize, tap } from 'rxjs/operators'; +import { MessageService } from '@shared'; + +@Injectable() +export class LoggingInterceptor implements HttpInterceptor { + constructor(private messenger: MessageService) {} + + intercept(req: HttpRequest, next: HttpHandler) { + const started = Date.now(); + let ok: string; + + // extend server response observable with logging + return next.handle(req).pipe( + tap( + // Succeeds when there is a response; ignore other events + event => (ok = event instanceof HttpResponse ? 'succeeded' : ''), + // Operation failed; error is an HttpErrorResponse + error => (ok = 'failed') + ), + // Log when response observable either completes or errors + finalize(() => { + const elapsed = Date.now() - started; + const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`; + this.messenger.add(msg); + }) + ); + } +} diff --git a/front/app/src/app/core/interceptors/noop-interceptor.ts b/front/app/src/app/core/interceptors/noop-interceptor.ts new file mode 100644 index 00000000..94430207 --- /dev/null +++ b/front/app/src/app/core/interceptors/noop-interceptor.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() +export class NoopInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req); + } +} diff --git a/front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts b/front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts new file mode 100644 index 00000000..65fb9617 --- /dev/null +++ b/front/app/src/app/core/interceptors/sanctum-interceptor.spec.ts @@ -0,0 +1,98 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { switchMap } from 'rxjs/operators'; +import { SanctumInterceptor } from './sanctum-interceptor'; +import { BASE_URL } from './base-url-interceptor'; +import { SANCTUM_PREFIX } from '@core/bootstrap/sanctum.service'; + +describe('SanctumInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + + const setBaseUrlAndSanctumPrefix = (baseUrl: string | null, sanctumPrefix: string | null) => { + TestBed.overrideProvider(BASE_URL, { useValue: baseUrl }); + TestBed.overrideProvider(SANCTUM_PREFIX, { useValue: sanctumPrefix }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: BASE_URL, useValue: null }, + { provide: SANCTUM_PREFIX, useValue: null }, + { provide: HTTP_INTERCEPTORS, useClass: SanctumInterceptor, multi: true }, + ], + }); + }); + + afterEach(() => httpMock.verify()); + + it('should get csrf cookie once', () => { + setBaseUrlAndSanctumPrefix(null, null); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('/sanctum/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); + + it('should get csrf cookie with base url', () => { + setBaseUrlAndSanctumPrefix('https://foo.bar/api', null); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('https://foo.bar/sanctum/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); + + it('should get csrf cookie with sanctum prefix', () => { + setBaseUrlAndSanctumPrefix(null, 'foobar'); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('/foobar/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); + + it('should get csrf cookie with base url and sanctum prefix', () => { + setBaseUrlAndSanctumPrefix('https://foo.bar/api', 'foobar'); + + http + .post('/auth/login', { + username: 'foo', + password: 'bar', + }) + .pipe(switchMap(() => http.get('/me'))) + .subscribe(data => expect(data).toEqual({ me: true })); + + httpMock.expectOne('https://foo.bar/foobar/csrf-cookie').flush({ cookie: true }); + httpMock.expectOne('/auth/login').flush({ login: true }); + httpMock.expectOne('/me').flush({ me: true }); + }); +}); diff --git a/front/app/src/app/core/interceptors/sanctum-interceptor.ts b/front/app/src/app/core/interceptors/sanctum-interceptor.ts new file mode 100644 index 00000000..f4dec383 --- /dev/null +++ b/front/app/src/app/core/interceptors/sanctum-interceptor.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { SanctumService } from '@core'; + +@Injectable() +export class SanctumInterceptor implements HttpInterceptor { + private ready = false; + + constructor(private sanctum: SanctumService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + if (!this.ready) { + this.ready = true; + + return this.sanctum.toObservable().pipe(switchMap(() => next.handle(request))); + } + + return next.handle(request); + } +} diff --git a/front/app/src/app/core/interceptors/settings-interceptor.spec.ts b/front/app/src/app/core/interceptors/settings-interceptor.spec.ts new file mode 100644 index 00000000..5fcf0ec9 --- /dev/null +++ b/front/app/src/app/core/interceptors/settings-interceptor.spec.ts @@ -0,0 +1,33 @@ +import { TestBed } from '@angular/core/testing'; + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { SettingsService } from '@core/bootstrap/settings.service'; +import { SettingsInterceptor } from './settings-interceptor'; + +describe('SettingsInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let settings: SettingsService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [{ provide: HTTP_INTERCEPTORS, useClass: SettingsInterceptor, multi: true }], + }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + settings = TestBed.inject(SettingsService); + }); + + it('should set accept language', () => { + settings.setLanguage('zh-TW'); + + http.get('/me').subscribe(); + const testRequest = httpMock.expectOne('/me'); + testRequest.flush({ me: true }); + + expect(testRequest.request.headers.get('Accept-Language')).toEqual('zh-TW'); + }); +}); diff --git a/front/app/src/app/core/interceptors/settings-interceptor.ts b/front/app/src/app/core/interceptors/settings-interceptor.ts new file mode 100644 index 00000000..36c1b93b --- /dev/null +++ b/front/app/src/app/core/interceptors/settings-interceptor.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { SettingsService } from '@core'; + +@Injectable() +export class SettingsInterceptor implements HttpInterceptor { + constructor(private settings: SettingsService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle( + request.clone({ + headers: request.headers.append('Accept-Language', this.settings.getLanguage()), + }) + ); + } +} diff --git a/front/app/src/app/core/interceptors/token-interceptor.spec.ts b/front/app/src/app/core/interceptors/token-interceptor.spec.ts new file mode 100644 index 00000000..f9763830 --- /dev/null +++ b/front/app/src/app/core/interceptors/token-interceptor.spec.ts @@ -0,0 +1,116 @@ +import { TestBed } from '@angular/core/testing'; + +import { TokenInterceptor } from './token-interceptor'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; +import { STATUS } from 'angular-in-memory-web-api'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Router } from '@angular/router'; +import { LocalStorageService, MemoryStorageService } from '@shared/services/storage.service'; +import { TokenService, User } from '@core/authentication'; +import { BASE_URL } from './base-url-interceptor'; + +describe('TokenInterceptor', () => { + let httpMock: HttpTestingController; + let http: HttpClient; + let router: Router; + let tokenService: TokenService; + const emptyFn = () => {}; + const baseUrl = 'https://foo.bar'; + const user: User = { id: 1, email: 'foo@bar.com' }; + + function init(url: string, access_token: string) { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + providers: [ + { provide: LocalStorageService, useClass: MemoryStorageService }, + { provide: BASE_URL, useValue: url }, + { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, + ], + }); + + httpMock = TestBed.inject(HttpTestingController); + http = TestBed.inject(HttpClient); + router = TestBed.inject(Router); + tokenService = TestBed.inject(TokenService).set({ access_token, token_type: 'bearer' }); + } + + function mockRequest(url: string, body?: any, headers?: any) { + http.get(url).subscribe(emptyFn, emptyFn, emptyFn); + const testRequest = httpMock.expectOne(url); + testRequest.flush(body ?? {}, headers ?? {}); + + return testRequest; + } + + afterEach(() => httpMock.verify()); + + it('should append token when url does not has http scheme', () => { + init('', 'token'); + + const headers = mockRequest('/me', user).request.headers; + + expect(headers.get('Authorization')).toEqual('Bearer token'); + }); + + it('should append token when url does not has http and base url not empty', () => { + init(baseUrl, 'token'); + + const headers = mockRequest('/me', user).request.headers; + + expect(headers.get('Authorization')).toEqual('Bearer token'); + }); + + it('should append token when url include base url', () => { + init(baseUrl, 'token'); + + const headers = mockRequest(`${baseUrl}/me`, user).request.headers; + + expect(headers.get('Authorization')).toEqual('Bearer token'); + }); + + it('should not append token when url not include baseUrl', () => { + init(baseUrl, 'token'); + + const headers = mockRequest('https://api.github.com', { success: true }).request.headers; + + expect(headers.has('Authorization')).toBeFalse(); + }); + + it('should not append token when base url is empty and url is not same site', () => { + init('', 'token'); + + const headers = mockRequest('https://api.github.com', { success: true }).request.headers; + + expect(headers.has('Authorization')).toBeFalse(); + }); + + it('should clear token when response status is unauthorized', () => { + init('', 'token'); + spyOn(tokenService, 'clear'); + + mockRequest('/me', {}, { status: STATUS.UNAUTHORIZED, statusText: 'Unauthorized' }); + + expect(tokenService.clear).toHaveBeenCalled(); + }); + + it('should navigate /auth/login when api url is /auth/logout and token is valid', () => { + init('', 'token'); + const navigateByUrl = spyOn(router, 'navigateByUrl'); + navigateByUrl.and.returnValue(Promise.resolve(true)); + + mockRequest('/auth/logout'); + + expect(navigateByUrl).toHaveBeenCalledWith('/auth/login'); + }); + + it('should navigate /auth/login when api url is /auth/logout and token is invalid', () => { + init('', ''); + const navigateByUrl = spyOn(router, 'navigateByUrl'); + navigateByUrl.and.returnValue(Promise.resolve(true)); + + mockRequest('/auth/logout'); + + expect(navigateByUrl).toHaveBeenCalledWith('/auth/login'); + }); +}); diff --git a/front/app/src/app/core/interceptors/token-interceptor.ts b/front/app/src/app/core/interceptors/token-interceptor.ts new file mode 100644 index 00000000..1e8c730e --- /dev/null +++ b/front/app/src/app/core/interceptors/token-interceptor.ts @@ -0,0 +1,71 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Router } from '@angular/router'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; +import { TokenService } from '@core/authentication'; +import { BASE_URL } from './base-url-interceptor'; + +@Injectable() +export class TokenInterceptor implements HttpInterceptor { + private hasHttpScheme = (url: string) => new RegExp('^http(s)?://', 'i').test(url); + + constructor( + private tokenService: TokenService, + private router: Router, + @Optional() @Inject(BASE_URL) private baseUrl?: string + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + const handler = () => { + if (request.url.includes('/auth/logout')) { + this.router.navigateByUrl('/auth/login'); + } + + if (this.router.url.includes('/auth/login')) { + this.router.navigateByUrl('/dashboard'); + } + }; + + if (this.tokenService.valid() && this.shouldAppendToken(request.url)) { + return next + .handle( + request.clone({ + headers: request.headers.append('Authorization', this.tokenService.getBearerToken()), + withCredentials: true, + }) + ) + .pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === 401) { + this.tokenService.clear(); + } + return throwError(error); + }), + tap(() => handler()) + ); + } + + return next.handle(request).pipe(tap(() => handler())); + } + + private shouldAppendToken(url: string) { + return !this.hasHttpScheme(url) || this.includeBaseUrl(url); + } + + private includeBaseUrl(url: string) { + if (!this.baseUrl) { + return false; + } + + const baseUrl = this.baseUrl.replace(/\/$/, ''); + + return new RegExp(`^${baseUrl}`, 'i').test(url); + } +} diff --git a/front/app/src/app/core/module-import-guard.ts b/front/app/src/app/core/module-import-guard.ts new file mode 100644 index 00000000..eb87efcf --- /dev/null +++ b/front/app/src/app/core/module-import-guard.ts @@ -0,0 +1,7 @@ +export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { + if (parentModule) { + throw new Error( + `${moduleName} has already been loaded. Import Core modules in the AppModule only.` + ); + } +} diff --git a/front/app/src/app/core/settings.ts b/front/app/src/app/core/settings.ts new file mode 100644 index 00000000..ab739602 --- /dev/null +++ b/front/app/src/app/core/settings.ts @@ -0,0 +1,23 @@ +export interface AppSettings { + navPos: 'side' | 'top'; + theme: 'light' | 'dark' | 'auto'; + dir: 'ltr' | 'rtl'; + showHeader: boolean; + headerPos: 'fixed' | 'static' | 'above'; + showUserPanel: boolean; + sidenavOpened: boolean; + sidenavCollapsed: boolean; + language: string; +} + +export const defaults: AppSettings = { + navPos: 'side', + theme: 'auto', + dir: 'ltr', + showHeader: true, + headerPos: 'fixed', + showUserPanel: true, + sidenavOpened: true, + sidenavCollapsed: false, + language: 'en-US', +}; diff --git a/front/app/src/app/fake-login.service.ts b/front/app/src/app/fake-login.service.ts new file mode 100644 index 00000000..b64d613e --- /dev/null +++ b/front/app/src/app/fake-login.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { of } from 'rxjs'; +import { admin, LoginService, Menu } from '@core'; +import { map } from 'rxjs/operators'; + +/** + * You should delete this file in the real APP. + */ +@Injectable() +export class FakeLoginService extends LoginService { + private token = { access_token: 'MW56YjMyOUAxNjMuY29tWm9uZ2Jpbg==', token_type: 'bearer' }; + + login() { + return of(this.token); + } + + refresh() { + return of(this.token); + } + + logout() { + return of({}); + } + + me() { + return of(admin); + } + + menu() { + return this.http + .get<{ menu: Menu[] }>('assets/data/menu.json?_t=' + Date.now()) + .pipe(map(res => res.menu)); + } +} diff --git a/front/app/src/app/formly-config.module.ts b/front/app/src/app/formly-config.module.ts new file mode 100644 index 00000000..52eaedd9 --- /dev/null +++ b/front/app/src/app/formly-config.module.ts @@ -0,0 +1,53 @@ +import { NgModule, ModuleWithProviders, Provider } from '@angular/core'; +import { SharedModule } from './shared/shared.module'; + +import { FormlyModule } from '@ngx-formly/core'; +import { FormlyFieldComboboxComponent } from './formly-templates'; +import { FormlyWrapperCardComponent, FormlyWrapperDivComponent } from './formly-wrappers'; +import { FormlyValidations } from './formly-validations'; + +/** + * Formly global configuration + */ +const formlyModuleProviders = FormlyModule.forRoot({ + types: [ + { + name: 'combobox', + component: FormlyFieldComboboxComponent, + wrappers: ['form-field'], + }, + ], + wrappers: [ + { + name: 'card', + component: FormlyWrapperCardComponent, + }, + { + name: 'div', + component: FormlyWrapperDivComponent, + }, + ], + validationMessages: [], +}).providers as Provider[]; + +@NgModule({ + imports: [SharedModule], + declarations: [ + FormlyFieldComboboxComponent, + FormlyWrapperCardComponent, + FormlyWrapperDivComponent, + ], + providers: [FormlyValidations], +}) +export class FormlyConfigModule { + constructor(formlyValidations: FormlyValidations) { + formlyValidations.init(); + } + + static forRoot(): ModuleWithProviders { + return { + ngModule: FormlyConfigModule, + providers: [formlyModuleProviders], + }; + } +} diff --git a/front/app/src/app/formly-templates.ts b/front/app/src/app/formly-templates.ts new file mode 100644 index 00000000..db6b2cf7 --- /dev/null +++ b/front/app/src/app/formly-templates.ts @@ -0,0 +1,45 @@ +import { ViewChild, ChangeDetectionStrategy, Component } from '@angular/core'; +import { FieldType } from '@ngx-formly/material/form-field'; +import { MtxSelect } from '@ng-matero/extensions/select'; +import { FieldTypeConfig } from '@ngx-formly/core'; + +/** + * This is just an example. + */ +@Component({ + selector: 'formly-field-combobox', + template: ` + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FormlyFieldComboboxComponent extends FieldType { + @ViewChild('select', { static: true }) select!: MtxSelect; + + get bindLabel() { + return typeof this.props.labelProp === 'string' ? this.props.labelProp : ''; + } + + get bindValue() { + return typeof this.props.valueProp === 'string' ? this.props.valueProp : undefined; + } + + // The original `onContainerClick` has been covered up in FieldType, so we should redefine it. + onContainerClick(event: MouseEvent) { + const target = event.target as HTMLElement; + if (/mat-form-field|mtx-select/g.test(target.parentElement?.classList[0] || '')) { + this.select.focus(); + this.select.open(); + } + } +} diff --git a/front/app/src/app/formly-validations.ts b/front/app/src/app/formly-validations.ts new file mode 100644 index 00000000..195e38f0 --- /dev/null +++ b/front/app/src/app/formly-validations.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { FormlyFieldConfig, FormlyConfig } from '@ngx-formly/core'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export class FormlyValidations { + constructor(private translate: TranslateService, private formlyConfig: FormlyConfig) {} + + init(): void { + // message without params + this.formlyConfig.addValidatorMessage('required', (_err, _field) => + this.translate.stream('validations.required') + ); + + // message with params + this.formlyConfig.addValidatorMessage('minLength', (err, field) => + this.minLengthValidationMessage(err, field, this.translate) + ); + this.formlyConfig.addValidatorMessage('maxLength', (err, field) => + this.maxLengthValidationMessage(err, field, this.translate) + ); + this.formlyConfig.addValidatorMessage('min', (err, field) => + this.minValidationMessage(err, field, this.translate) + ); + this.formlyConfig.addValidatorMessage('max', (err, field) => + this.maxValidationMessage(err, field, this.translate) + ); + } + + private minLengthValidationMessage( + err: any, + field: FormlyFieldConfig, + translate: TranslateService + ) { + return translate.stream('validations.minlength', { number: field.props?.minLength }); + } + + private maxLengthValidationMessage( + err: any, + field: FormlyFieldConfig, + translate: TranslateService + ) { + return translate.stream('validations.maxlength', { number: field.props?.maxLength }); + } + + private minValidationMessage(err: any, field: FormlyFieldConfig, translate: TranslateService) { + return translate.stream('validations.min', { number: field.props?.min }); + } + + private maxValidationMessage(err: any, field: FormlyFieldConfig, translate: TranslateService) { + return translate.stream('validations.max', { number: field.props?.max }); + } +} diff --git a/front/app/src/app/formly-wrappers.ts b/front/app/src/app/formly-wrappers.ts new file mode 100644 index 00000000..6125c4e9 --- /dev/null +++ b/front/app/src/app/formly-wrappers.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { FieldWrapper } from '@ngx-formly/core'; + +/** + * This is just an example. + */ +@Component({ + selector: 'formly-wrapper-card', + template: ` +
+

Its time to party

+

{{ props.label }}

+
+ +
+
+ `, +}) +export class FormlyWrapperCardComponent extends FieldWrapper {} + +@Component({ + selector: 'formly-wrapper-div', + template: ` +
+ +
+ `, +}) +export class FormlyWrapperDivComponent extends FieldWrapper {} diff --git a/front/app/src/app/material-extensions.module.ts b/front/app/src/app/material-extensions.module.ts new file mode 100644 index 00000000..9baa9281 --- /dev/null +++ b/front/app/src/app/material-extensions.module.ts @@ -0,0 +1,66 @@ +import { NgModule } from '@angular/core'; + +import { MtxAlertModule } from '@ng-matero/extensions/alert'; +import { MtxButtonModule } from '@ng-matero/extensions/button'; +import { MtxCheckboxGroupModule } from '@ng-matero/extensions/checkbox-group'; +import { MtxColorpickerModule } from '@ng-matero/extensions/colorpicker'; +import { MtxDatetimepickerModule } from '@ng-matero/extensions/datetimepicker'; +import { MtxDialogModule } from '@ng-matero/extensions/dialog'; +import { MtxDrawerModule } from '@ng-matero/extensions/drawer'; +import { MtxGridModule } from '@ng-matero/extensions/grid'; +import { MtxLoaderModule } from '@ng-matero/extensions/loader'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; +import { MtxProgressModule } from '@ng-matero/extensions/progress'; +import { MtxSelectModule } from '@ng-matero/extensions/select'; +import { MtxSliderModule } from '@ng-matero/extensions/slider'; +import { MtxSplitModule } from '@ng-matero/extensions/split'; +import { MtxTooltipModule } from '@ng-matero/extensions/tooltip'; +import { MTX_DATETIME_FORMATS } from '@ng-matero/extensions/core'; +import { MtxMomentDatetimeModule } from '@ng-matero/extensions-moment-adapter'; + +@NgModule({ + exports: [ + MtxAlertModule, + MtxButtonModule, + MtxCheckboxGroupModule, + MtxColorpickerModule, + MtxDatetimepickerModule, + MtxDialogModule, + MtxDrawerModule, + MtxGridModule, + MtxLoaderModule, + MtxPopoverModule, + MtxProgressModule, + MtxSelectModule, + MtxSliderModule, + MtxSplitModule, + MtxTooltipModule, + MtxMomentDatetimeModule, // <= You can import the other adapter you need (luxon, date-fns) + ], + providers: [ + { + provide: MTX_DATETIME_FORMATS, + useValue: { + parse: { + dateInput: 'YYYY-MM-DD', + yearInput: 'YYYY', + monthInput: 'MMMM', + datetimeInput: 'YYYY-MM-DD HH:mm', + timeInput: 'HH:mm', + }, + display: { + dateInput: 'YYYY-MM-DD', + yearInput: 'YYYY', + monthInput: 'MMMM', + datetimeInput: 'YYYY-MM-DD HH:mm', + timeInput: 'HH:mm', + monthYearLabel: 'YYYY MMMM', + dateA11yLabel: 'LL', + monthYearA11yLabel: 'MMMM YYYY', + popupHeaderDateLabel: 'MMM DD, ddd', + }, + }, + }, + ], +}) +export class MaterialExtensionsModule {} diff --git a/front/app/src/app/material.module.ts b/front/app/src/app/material.module.ts new file mode 100644 index 00000000..39155df7 --- /dev/null +++ b/front/app/src/app/material.module.ts @@ -0,0 +1,117 @@ +import { NgModule } from '@angular/core'; + +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatCardModule } from '@angular/material/card'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatRippleModule, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { + MatDialogConfig, + MatDialogModule, + MAT_DIALOG_DEFAULT_OPTIONS, +} from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatListModule } from '@angular/material/list'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSortModule } from '@angular/material/sort'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatTableModule } from '@angular/material/table'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTreeModule } from '@angular/material/tree'; +import { MatMomentDateModule } from '@angular/material-moment-adapter'; + +import { PaginatorI18nService } from '@shared/services/paginator-i18n.service'; + +@NgModule({ + exports: [ + MatAutocompleteModule, + MatBadgeModule, + MatBottomSheetModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatCheckboxModule, + MatChipsModule, + MatStepperModule, + MatDatepickerModule, + MatMomentDateModule, + MatDialogModule, + MatDividerModule, + MatExpansionModule, + MatFormFieldModule, + MatGridListModule, + MatIconModule, + MatInputModule, + MatListModule, + MatMenuModule, + MatPaginatorModule, + MatProgressBarModule, + MatProgressSpinnerModule, + MatRadioModule, + MatRippleModule, + MatSelectModule, + MatSidenavModule, + MatSliderModule, + MatSlideToggleModule, + MatSnackBarModule, + MatSortModule, + MatTableModule, + MatTabsModule, + MatToolbarModule, + MatTooltipModule, + MatTreeModule, + ], + providers: [ + { + provide: MatPaginatorIntl, + deps: [PaginatorI18nService], + useFactory: (paginatorI18nSrv: PaginatorI18nService) => paginatorI18nSrv.getPaginatorIntl(), + }, + { + provide: MAT_DIALOG_DEFAULT_OPTIONS, + useValue: { + ...new MatDialogConfig(), + }, + }, + { + provide: MAT_DATE_LOCALE, + useFactory: () => navigator.language, // <= This will be overrided by runtime setting + }, + { + provide: MAT_DATE_FORMATS, + useValue: { + parse: { + dateInput: 'YYYY-MM-DD', + }, + display: { + dateInput: 'YYYY-MM-DD', + monthYearLabel: 'YYYY MMM', + dateA11yLabel: 'LL', + monthYearA11yLabel: 'YYYY MMM', + }, + }, + }, + ], +}) +export class MaterialModule {} diff --git a/front/app/src/app/routes/dashboard/dashboard.component.html b/front/app/src/app/routes/dashboard/dashboard.component.html new file mode 100644 index 00000000..f2649e73 --- /dev/null +++ b/front/app/src/app/routes/dashboard/dashboard.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/app/src/app/auth/auth.component.css b/front/app/src/app/routes/dashboard/dashboard.component.scss similarity index 100% rename from front/app/src/app/auth/auth.component.css rename to front/app/src/app/routes/dashboard/dashboard.component.scss diff --git a/front/app/src/app/routes/dashboard/dashboard.component.ts b/front/app/src/app/routes/dashboard/dashboard.component.ts new file mode 100644 index 00000000..51790451 --- /dev/null +++ b/front/app/src/app/routes/dashboard/dashboard.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DashboardComponent implements OnInit { + constructor(private cdr: ChangeDetectorRef) {} + + ngOnInit() {} +} diff --git a/front/app/src/app/routes/routes-routing.module.ts b/front/app/src/app/routes/routes-routing.module.ts new file mode 100644 index 00000000..896ba3aa --- /dev/null +++ b/front/app/src/app/routes/routes-routing.module.ts @@ -0,0 +1,48 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { environment } from '@env/environment'; + +import { AdminLayoutComponent } from '../theme/admin-layout/admin-layout.component'; +import { AuthLayoutComponent } from '../theme/auth-layout/auth-layout.component'; +import { DashboardComponent } from './dashboard/dashboard.component'; +import { LoginComponent } from './sessions/login/login.component'; +import { RegisterComponent } from './sessions/register/register.component'; +import { Error403Component } from './sessions/403.component'; +import { Error404Component } from './sessions/404.component'; +import { Error500Component } from './sessions/500.component'; +import { AuthGuard } from '@core'; + +const routes: Routes = [ + { + path: '', + component: AdminLayoutComponent, + canActivate: [AuthGuard], + canActivateChild: [AuthGuard], + children: [ + { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent }, + { path: '403', component: Error403Component }, + { path: '404', component: Error404Component }, + { path: '500', component: Error500Component }, + ], + }, + { + path: 'auth', + component: AuthLayoutComponent, + children: [ + { path: 'login', component: LoginComponent }, + { path: 'register', component: RegisterComponent }, + ], + }, + { path: '**', redirectTo: 'dashboard' }, +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(routes, { + useHash: environment.useHash, + }), + ], + exports: [RouterModule], +}) +export class RoutesRoutingModule {} diff --git a/front/app/src/app/routes/routes.module.ts b/front/app/src/app/routes/routes.module.ts new file mode 100644 index 00000000..b2f943af --- /dev/null +++ b/front/app/src/app/routes/routes.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '@shared/shared.module'; +import { RoutesRoutingModule } from './routes-routing.module'; + +import { DashboardComponent } from './dashboard/dashboard.component'; +import { LoginComponent } from './sessions/login/login.component'; +import { RegisterComponent } from './sessions/register/register.component'; +import { Error403Component } from './sessions/403.component'; +import { Error404Component } from './sessions/404.component'; +import { Error500Component } from './sessions/500.component'; + +const COMPONENTS: any[] = [ + DashboardComponent, + LoginComponent, + RegisterComponent, + Error403Component, + Error404Component, + Error500Component, +]; +const COMPONENTS_DYNAMIC: any[] = []; + +@NgModule({ + imports: [SharedModule, RoutesRoutingModule], + declarations: [...COMPONENTS, ...COMPONENTS_DYNAMIC], +}) +export class RoutesModule {} diff --git a/front/app/src/app/routes/sessions/403.component.ts b/front/app/src/app/routes/sessions/403.component.ts new file mode 100644 index 00000000..81f76f6c --- /dev/null +++ b/front/app/src/app/routes/sessions/403.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-error-403', + template: ` + + `, +}) +export class Error403Component {} diff --git a/front/app/src/app/routes/sessions/404.component.ts b/front/app/src/app/routes/sessions/404.component.ts new file mode 100644 index 00000000..5d1fecd6 --- /dev/null +++ b/front/app/src/app/routes/sessions/404.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-error-404', + template: ` + + `, +}) +export class Error404Component {} diff --git a/front/app/src/app/routes/sessions/500.component.ts b/front/app/src/app/routes/sessions/500.component.ts new file mode 100644 index 00000000..024edf4f --- /dev/null +++ b/front/app/src/app/routes/sessions/500.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-error-500', + template: ` + + + `, +}) +export class Error500Component {} diff --git a/front/app/src/app/routes/sessions/login/login.component.html b/front/app/src/app/routes/sessions/login/login.component.html new file mode 100644 index 00000000..8a7fc1dc --- /dev/null +++ b/front/app/src/app/routes/sessions/login/login.component.html @@ -0,0 +1,45 @@ +
+ + + {{'login.title' | translate}}! + + + +
+ + {{'login.username' | translate}}: ng-matero + + + {{'login.please_enter' | translate}} + ng-matero + + {{ username.errors?.remote }} + + + + + {{'login.password' | translate}}: ng-matero + + + {{'login.please_enter' | translate}} + ng-matero + + {{ password.errors?.remote }} + + + + {{'login.remember_me' | translate}} + + + + +
{{'login.have_no_account' | translate}}? + {{'login.create_one' | translate}} +
+
+
+
+
diff --git a/front/app/src/app/routes/sessions/login/login.component.scss b/front/app/src/app/routes/sessions/login/login.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/front/app/src/app/routes/sessions/login/login.component.ts b/front/app/src/app/routes/sessions/login/login.component.ts new file mode 100644 index 00000000..679f97c8 --- /dev/null +++ b/front/app/src/app/routes/sessions/login/login.component.ts @@ -0,0 +1,58 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { HttpErrorResponse } from '@angular/common/http'; +import { filter } from 'rxjs/operators'; +import { AuthService } from '@core/authentication'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.scss'], +}) +export class LoginComponent { + isSubmitting = false; + + loginForm = this.fb.nonNullable.group({ + username: ['ng-matero', [Validators.required]], + password: ['ng-matero', [Validators.required]], + rememberMe: [false], + }); + + constructor(private fb: FormBuilder, private router: Router, private auth: AuthService) {} + + get username() { + return this.loginForm.get('username')!; + } + + get password() { + return this.loginForm.get('password')!; + } + + get rememberMe() { + return this.loginForm.get('rememberMe')!; + } + + login() { + this.isSubmitting = true; + + this.auth + .login(this.username.value, this.password.value, this.rememberMe.value) + .pipe(filter(authenticated => authenticated)) + .subscribe( + () => this.router.navigateByUrl('/'), + (errorRes: HttpErrorResponse) => { + if (errorRes.status === 422) { + const form = this.loginForm; + const errors = errorRes.error.errors; + Object.keys(errors).forEach(key => { + form.get(key === 'email' ? 'username' : key)?.setErrors({ + remote: errors[key][0], + }); + }); + } + this.isSubmitting = false; + } + ); + } +} diff --git a/front/app/src/app/routes/sessions/register/register.component.html b/front/app/src/app/routes/sessions/register/register.component.html new file mode 100644 index 00000000..64f61c75 --- /dev/null +++ b/front/app/src/app/routes/sessions/register/register.component.html @@ -0,0 +1,52 @@ +
+ + + + {{'register.welcome' | translate}},
+ {{'register.title' | translate}} +
+
+ + +
+ + {{'login.username' | translate}} + + + {{'validations.required' | translate}} + + + + + {{'login.password' | translate}} + + + {{'validations.required' | translate}} + + + + + {{'register.confirm_password' | translate}} + + + {{'validations.required' | translate}} + + + {{'validations.inconsistent'}} + + + + {{'register.agree' | translate}} + + + +
{{'register.have_an_account' | translate}}? + {{'login.login' | translate}} +
+
+
+
+
diff --git a/front/app/src/app/routes/sessions/register/register.component.scss b/front/app/src/app/routes/sessions/register/register.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/front/app/src/app/routes/sessions/register/register.component.ts b/front/app/src/app/routes/sessions/register/register.component.ts new file mode 100644 index 00000000..99db87ba --- /dev/null +++ b/front/app/src/app/routes/sessions/register/register.component.ts @@ -0,0 +1,39 @@ +import { Component } from '@angular/core'; +import { FormBuilder, Validators, AbstractControl } from '@angular/forms'; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.scss'], +}) +export class RegisterComponent { + registerForm = this.fb.nonNullable.group( + { + username: ['', [Validators.required]], + password: ['', [Validators.required]], + confirmPassword: ['', [Validators.required]], + }, + { + validators: [this.matchValidator('password', 'confirmPassword')], + } + ); + + constructor(private fb: FormBuilder) {} + + matchValidator(source: string, target: string) { + return (control: AbstractControl) => { + const sourceControl = control.get(source)!; + const targetControl = control.get(target)!; + if (targetControl.errors && !targetControl.errors.mismatch) { + return null; + } + if (sourceControl.value !== targetControl.value) { + targetControl.setErrors({ mismatch: true }); + return { mismatch: true }; + } else { + targetControl.setErrors(null); + return null; + } + }; + } +} diff --git a/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html new file mode 100644 index 00000000..27c3068b --- /dev/null +++ b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.html @@ -0,0 +1,12 @@ + diff --git a/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss new file mode 100644 index 00000000..39f0958e --- /dev/null +++ b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.scss @@ -0,0 +1,33 @@ +.matero-breadcrumb { + display: flex; + flex-wrap: wrap; + margin-bottom: 1rem; + padding: 0; + font-size: .875rem; + list-style: none; +} + +.matero-breadcrumb-item { + line-height: 18px; + text-transform: capitalize; + + > * { + vertical-align: middle; + } + + > a.link { + color: currentColor; + + &:hover { + color: currentColor; + text-decoration: underline; + } + } + + > .chevron { + width: 18px; + height: 18px; + font-size: 18px; + user-select: none; + } +} diff --git a/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 00000000..bbc59972 --- /dev/null +++ b/front/app/src/app/shared/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { MenuService } from '@core/bootstrap/menu.service'; + +@Component({ + selector: 'breadcrumb', + templateUrl: './breadcrumb.component.html', + styleUrls: ['./breadcrumb.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class BreadcrumbComponent implements OnInit { + @Input() nav: string[] = []; + + constructor(private router: Router, private menu: MenuService) {} + + ngOnInit() { + this.nav = Array.isArray(this.nav) ? this.nav : []; + + if (this.nav.length === 0) { + this.genBreadcrumb(); + } + } + + trackByNavlink(index: number, navLink: string): string { + return navLink; + } + + genBreadcrumb() { + const routes = this.router.url.slice(1).split('/'); + this.nav = this.menu.getLevel(routes); + this.nav.unshift('home'); + } +} diff --git a/front/app/src/app/shared/components/error-code/_error-code-theme.scss b/front/app/src/app/shared/components/error-code/_error-code-theme.scss new file mode 100644 index 00000000..3971559a --- /dev/null +++ b/front/app/src/app/shared/components/error-code/_error-code-theme.scss @@ -0,0 +1,11 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-error-code { + color: mat.get-color-from-palette($foreground, text); + } +} diff --git a/front/app/src/app/shared/components/error-code/_long-shadow.scss b/front/app/src/app/shared/components/error-code/_long-shadow.scss new file mode 100644 index 00000000..24dac9a8 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/_long-shadow.scss @@ -0,0 +1,55 @@ +// Long Shadow +// +// https://codepen.io/c_fitzmaurice/pen/ZYJeRY + +@use 'sass:color'; +@use 'sass:list'; +@use 'sass:meta'; +@use 'sass:map'; +@use 'sass:math'; + +@function long-shadow($direction, $length, $color, $fade: false, $shadow-count: 100) { + $shadows: (); + $conversion-map: ( + to top: 180deg, + to top right: 135deg, + to right top: 135deg, + to right: 90deg, + to bottom right: 45deg, + to right bottom: 45deg, + to bottom: 0deg, + to bottom left: 315deg, + to left bottom: 315deg, + to left: 270deg, + to left top: 225deg, + to top left: 225deg + ); + + @if map-has-key($conversion-map, $direction) { + $direction: map.get($conversion-map, $direction); + } + + @for $i from 1 through $shadow-count { + $current-step: math.div($i * $length, $shadow-count); + $current-color: if( + not $fade, + $color, + if( + meta.type-of($fade) == 'color', + color.mix($fade, $color, math.div($i, $shadow-count) * 100%), + color.rgba($color, 1 - math.div($i, $shadow-count)) + ) + ); + + $shadows: list.append( + $shadows, + (math.sin(0deg + $direction) * $current-step) + (math.cos(0deg + $direction) * $current-step) + 0 + $current-color, + 'comma' + ); + } + + @return $shadows; +} diff --git a/front/app/src/app/shared/components/error-code/error-code.component.html b/front/app/src/app/shared/components/error-code/error-code.component.html new file mode 100644 index 00000000..76b02749 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/error-code.component.html @@ -0,0 +1,6 @@ +
+
{{code}}
+
{{title}}
+
{{message}}
+ +
\ No newline at end of file diff --git a/front/app/src/app/shared/components/error-code/error-code.component.scss b/front/app/src/app/shared/components/error-code/error-code.component.scss new file mode 100644 index 00000000..440ac0c1 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/error-code.component.scss @@ -0,0 +1,32 @@ +@use 'long-shadow'; + +.matero-error-wrap { + text-align: center; +} + +.matero-error-code { + padding: 20px 0; + font-size: 150px; + text-shadow: + long-shadow.long-shadow( + $direction: 45deg, + $length: 60px, + $color: rgba(0, 0, 0, .03), + $fade: rgba(0, 0, 0, .0015), + $shadow-count: 20 + ); +} + +.matero-error-title { + margin: 0 0 16px; + font-weight: 500; + font-size: 20px; + line-height: 32px; +} + +.matero-error-message { + margin: 0 0 16px; + font-weight: 400; + font-size: 16px; + line-height: 28px; +} diff --git a/front/app/src/app/shared/components/error-code/error-code.component.ts b/front/app/src/app/shared/components/error-code/error-code.component.ts new file mode 100644 index 00000000..51cdce54 --- /dev/null +++ b/front/app/src/app/shared/components/error-code/error-code.component.ts @@ -0,0 +1,13 @@ +import { Component, ViewEncapsulation, Input } from '@angular/core'; + +@Component({ + selector: 'error-code', + templateUrl: './error-code.component.html', + styleUrls: ['./error-code.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class ErrorCodeComponent { + @Input() code = ''; + @Input() title = ''; + @Input() message = ''; +} diff --git a/front/app/src/app/shared/components/page-header/page-header.component.html b/front/app/src/app/shared/components/page-header/page-header.component.html new file mode 100644 index 00000000..94d9cb98 --- /dev/null +++ b/front/app/src/app/shared/components/page-header/page-header.component.html @@ -0,0 +1,4 @@ +
+

{{title | translate}} {{subtitle}}

+ +
diff --git a/front/app/src/app/shared/components/page-header/page-header.component.scss b/front/app/src/app/shared/components/page-header/page-header.component.scss new file mode 100644 index 00000000..5c7fe177 --- /dev/null +++ b/front/app/src/app/shared/components/page-header/page-header.component.scss @@ -0,0 +1,18 @@ +.matero-page-header { + display: block; + margin: -16px -16px 16px; + padding: 16px; + color: #fff; + background-color: #3f51b5; + + .matero-breadcrumb { + margin-top: 8px; + margin-bottom: 0; + } +} + +.matero-page-title { + margin: 0; + font-weight: 400; + font-size: 24px; +} diff --git a/front/app/src/app/shared/components/page-header/page-header.component.ts b/front/app/src/app/shared/components/page-header/page-header.component.ts new file mode 100644 index 00000000..bf27e9e2 --- /dev/null +++ b/front/app/src/app/shared/components/page-header/page-header.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit, ViewEncapsulation, Input, HostBinding } from '@angular/core'; +import { MenuService } from '@core/bootstrap/menu.service'; +import { Router } from '@angular/router'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'page-header', + templateUrl: './page-header.component.html', + styleUrls: ['./page-header.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class PageHeaderComponent implements OnInit { + @HostBinding('class') class = 'matero-page-header'; + + @Input() title = ''; + @Input() subtitle = ''; + @Input() nav: string[] = []; + @Input() + get hideBreadcrumb() { + return this._hideBreadCrumb; + } + set hideBreadcrumb(value: boolean) { + this._hideBreadCrumb = coerceBooleanProperty(value); + } + private _hideBreadCrumb = false; + + constructor(private router: Router, private menu: MenuService) {} + + ngOnInit() { + this.nav = Array.isArray(this.nav) ? this.nav : []; + + if (this.nav.length === 0) { + this.genBreadcrumb(); + } + + this.title = this.title || this.nav[this.nav.length - 1]; + } + + genBreadcrumb() { + const routes = this.router.url.slice(1).split('/'); + this.nav = this.menu.getLevel(routes); + this.nav.unshift('home'); + } + + static ngAcceptInputType_hideBreadcrumb: BooleanInput; +} diff --git a/front/app/src/app/shared/directives/disable-control.directive.ts b/front/app/src/app/shared/directives/disable-control.directive.ts new file mode 100644 index 00000000..a55ee2d9 --- /dev/null +++ b/front/app/src/app/shared/directives/disable-control.directive.ts @@ -0,0 +1,14 @@ +import { NgControl } from '@angular/forms'; +import { Directive, Input, SkipSelf, Optional } from '@angular/core'; + +@Directive({ + selector: '[disableControl]', +}) +export class DisableControlDirective { + @Input() set disableControl(condition: boolean) { + const action = condition ? 'disable' : 'enable'; + this.ngControl.control?.[action](); + } + + constructor(@Optional() @SkipSelf() private ngControl: NgControl) {} +} diff --git a/front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts b/front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts new file mode 100644 index 00000000..3d497005 --- /dev/null +++ b/front/app/src/app/shared/in-mem/in-mem-data.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { InMemDataService } from './in-mem-data.service'; + +describe('InMemDataService', () => { + let service: InMemDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(InMemDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/front/app/src/app/shared/in-mem/in-mem-data.service.ts b/front/app/src/app/shared/in-mem/in-mem-data.service.ts new file mode 100644 index 00000000..75933714 --- /dev/null +++ b/front/app/src/app/shared/in-mem/in-mem-data.service.ts @@ -0,0 +1,209 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest } from '@angular/common/http'; +import { InMemoryDbService, RequestInfo, STATUS } from 'angular-in-memory-web-api'; +import { from, Observable } from 'rxjs'; +import { ajax } from 'rxjs/ajax'; +import { find, map, switchMap } from 'rxjs/operators'; +import { environment } from '@env/environment'; +import { base64, currentTimestamp, filterObject, User } from '@core/authentication'; + +class JWT { + generate(user: User) { + const expiresIn = 3600; + const refreshTokenExpiresIn = 86400; + + return filterObject({ + access_token: this.createToken(user, expiresIn), + token_type: 'bearer', + expires_in: user.refresh_token ? expiresIn : undefined, + refresh_token: user.refresh_token ? this.createToken(user, refreshTokenExpiresIn) : undefined, + }); + } + + getUser(req: HttpRequest) { + let token = ''; + + if (req.body?.refresh_token) { + token = req.body.refresh_token; + } else if (req.headers.has('Authorization')) { + const authorization = req.headers.get('Authorization'); + const result = (authorization as string).split(' '); + token = result[1]; + } + + try { + const now = new Date(); + const data = JWT.parseToken(token); + + return JWT.isExpired(data, now) ? null : data.user; + } catch (e) { + return null; + } + } + + createToken(user: User, expiresIn = 0) { + const exp = user.refresh_token ? currentTimestamp() + expiresIn : undefined; + + return [ + base64.encode(JSON.stringify({ typ: 'JWT', alg: 'HS256' })), + base64.encode(JSON.stringify(filterObject(Object.assign({ exp, user })))), + base64.encode('ng-matero'), + ].join('.'); + } + + private static parseToken(accessToken: string) { + const [, payload] = accessToken.split('.'); + + return JSON.parse(base64.decode(payload)); + } + + private static isExpired(data: any, now: Date) { + const expiresIn = new Date(); + expiresIn.setTime(data.exp * 1000); + const diff = this.dateToSeconds(expiresIn) - this.dateToSeconds(now); + + return diff <= 0; + } + + private static dateToSeconds(date: Date) { + return Math.ceil(date.getTime() / 1000); + } +} + +const jwt = new JWT(); + +function is(reqInfo: RequestInfo, path: string) { + if (environment.baseUrl) { + return false; + } + + return new RegExp(`${path}(/)?$`, 'i').test(reqInfo.req.url); +} + +@Injectable({ + providedIn: 'root', +}) +export class InMemDataService implements InMemoryDbService { + private users: User[] = [ + { + id: 1, + username: 'ng-matero', + password: 'ng-matero', + name: 'Zongbin', + email: 'nzb329@163.com', + avatar: './assets/images/avatar.jpg', + }, + { + id: 2, + username: 'recca0120', + password: 'password', + name: 'recca0120', + email: 'recca0120@gmail.com', + avatar: './assets/images/avatars/avatar-10.jpg', + refresh_token: true, + }, + ]; + + createDb( + reqInfo?: RequestInfo + ): + | Record + | Observable> + | Promise> { + return { users: this.users }; + } + + get(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + + if (is(reqInfo, 'sanctum/csrf-cookie')) { + const response = { headers, url, status: STATUS.NO_CONTENT, body: {} }; + + return reqInfo.utils.createResponse$(() => response); + } + + if (is(reqInfo, 'me/menu')) { + return ajax('assets/data/menu.json?_t=' + Date.now()).pipe( + map((response: any) => { + return { headers, url, status: STATUS.OK, body: { menu: response.response.menu } }; + }), + switchMap(response => reqInfo.utils.createResponse$(() => response)) + ); + } + + if (is(reqInfo, 'me')) { + const user = jwt.getUser(reqInfo.req as HttpRequest); + const result = user + ? { status: STATUS.OK, body: user } + : { status: STATUS.UNAUTHORIZED, body: {} }; + const response = Object.assign({ headers, url }, result); + + return reqInfo.utils.createResponse$(() => response); + } + + return; + } + + post(reqInfo: RequestInfo) { + if (is(reqInfo, 'auth/login')) { + return this.login(reqInfo); + } + + if (is(reqInfo, 'auth/refresh')) { + return this.refresh(reqInfo); + } + + if (is(reqInfo, 'auth/logout')) { + return this.logout(reqInfo); + } + + return; + } + + private login(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + const req = reqInfo.req as HttpRequest; + const { username, password } = req.body; + + return from(this.users).pipe( + find(user => user.username === username || user.email === username), + map(user => { + if (!user) { + return { headers, url, status: STATUS.UNAUTHORIZED, body: {} }; + } + + if (user.password !== password) { + const result = { + status: STATUS.UNPROCESSABLE_ENTRY, + error: { errors: { password: ['The provided password is incorrect.'] } }, + }; + + return Object.assign({ headers, url }, result); + } + + const currentUser = Object.assign({}, user); + delete currentUser.password; + return { headers, url, status: STATUS.OK, body: jwt.generate(currentUser) }; + }), + switchMap(response => reqInfo.utils.createResponse$(() => response)) + ); + } + + private refresh(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + const user = jwt.getUser(reqInfo.req as HttpRequest); + const result = user + ? { status: STATUS.OK, body: jwt.generate(user) } + : { status: STATUS.UNAUTHORIZED, body: {} }; + const response = Object.assign({ headers, url }, result); + + return reqInfo.utils.createResponse$(() => response); + } + + private logout(reqInfo: RequestInfo) { + const { headers, url } = reqInfo; + const response = { headers, url, status: STATUS.OK, body: {} }; + + return reqInfo.utils.createResponse$(() => response); + } +} diff --git a/front/app/src/app/shared/index.ts b/front/app/src/app/shared/index.ts new file mode 100644 index 00000000..b1382ae7 --- /dev/null +++ b/front/app/src/app/shared/index.ts @@ -0,0 +1,12 @@ +// Module +export * from './shared.module'; + +// Services +export * from './services/directionality.service'; +export * from './services/message.service'; +export * from './services/storage.service'; +export * from './services/paginator-i18n.service'; + +// Utils +export * from './utils/colors'; +export * from './utils/icons'; diff --git a/front/app/src/app/shared/pipes/safe-url.pipe.ts b/front/app/src/app/shared/pipes/safe-url.pipe.ts new file mode 100644 index 00000000..3869d9a9 --- /dev/null +++ b/front/app/src/app/shared/pipes/safe-url.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Pipe({ name: 'safeUrl' }) +export class SafeUrlPipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) {} + transform(url: string) { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } +} diff --git a/front/app/src/app/shared/pipes/to-observable.pipe.ts b/front/app/src/app/shared/pipes/to-observable.pipe.ts new file mode 100644 index 00000000..eaed0de6 --- /dev/null +++ b/front/app/src/app/shared/pipes/to-observable.pipe.ts @@ -0,0 +1,9 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Observable, of, isObservable } from 'rxjs'; + +@Pipe({ name: 'toObservable' }) +export class ToObservablePipe implements PipeTransform { + transform(value: Observable | unknown): Observable { + return isObservable(value) ? value : of(value); + } +} diff --git a/front/app/src/app/shared/services/directionality.service.ts b/front/app/src/app/shared/services/directionality.service.ts new file mode 100644 index 00000000..8b0e9d1b --- /dev/null +++ b/front/app/src/app/shared/services/directionality.service.ts @@ -0,0 +1,22 @@ +import { Direction, Directionality } from '@angular/cdk/bidi'; +import { EventEmitter, Injectable, OnDestroy } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class AppDirectionality implements Directionality, OnDestroy { + readonly change = new EventEmitter(); + + get value(): Direction { + return this._value; + } + set value(value: Direction) { + this._value = value; + this.change.next(value); + } + private _value: Direction = 'ltr'; + + ngOnDestroy() { + this.change.complete(); + } +} diff --git a/front/app/src/app/shared/services/message.service.ts b/front/app/src/app/shared/services/message.service.ts new file mode 100644 index 00000000..d72412e1 --- /dev/null +++ b/front/app/src/app/shared/services/message.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class MessageService { + messages: string[] = []; + + add(message: string) { + this.messages.push(message); + } + + clear() { + this.messages = []; + } +} diff --git a/front/app/src/app/shared/services/paginator-i18n.service.ts b/front/app/src/app/shared/services/paginator-i18n.service.ts new file mode 100644 index 00000000..a579a35d --- /dev/null +++ b/front/app/src/app/shared/services/paginator-i18n.service.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@angular/core'; +import { MatPaginatorIntl } from '@angular/material/paginator'; +import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; + +@Injectable({ + providedIn: 'root', +}) +export class PaginatorI18nService { + paginatorIntl = new MatPaginatorIntl(); + + constructor(private translate: TranslateService) { + this.translate.onLangChange.subscribe((event: LangChangeEvent) => this.getPaginatorIntl()); + } + + getPaginatorIntl() { + this.paginatorIntl.itemsPerPageLabel = this.translate.instant('paginator.items_per_page_label'); + this.paginatorIntl.previousPageLabel = this.translate.instant('paginator.previous_page_label'); + this.paginatorIntl.nextPageLabel = this.translate.instant('paginator.next_page_label'); + this.paginatorIntl.firstPageLabel = this.translate.instant('paginator.first_page_label'); + this.paginatorIntl.lastPageLabel = this.translate.instant('paginator.last_page_label'); + this.paginatorIntl.getRangeLabel = this.getRangeLabel.bind(this); + + this.paginatorIntl.changes.next(); + + return this.paginatorIntl; + } + + private getRangeLabel(page: number, pageSize: number, length: number): string { + if (length === 0 || pageSize === 0) { + return this.translate.instant('paginator.range_page_label_1', { length }); + } + length = Math.max(length, 0); + + const startIndex = page * pageSize; + // If the start index exceeds the list length, do not try and fix the end index to the end. + const endIndex = + startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize; + + return this.translate.instant('paginator.range_page_label_2', { + startIndex: startIndex + 1, + endIndex, + length, + }); + } +} diff --git a/front/app/src/app/shared/services/storage.service.ts b/front/app/src/app/shared/services/storage.service.ts new file mode 100644 index 00000000..66ac4b53 --- /dev/null +++ b/front/app/src/app/shared/services/storage.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class LocalStorageService { + get(key: string) { + return JSON.parse(localStorage.getItem(key) || '{}') || {}; + } + + set(key: string, value: any): boolean { + localStorage.setItem(key, JSON.stringify(value)); + + return true; + } + + has(key: string): boolean { + return !!localStorage.getItem(key); + } + + remove(key: string) { + localStorage.removeItem(key); + } + + clear() { + localStorage.clear(); + } +} + +export class MemoryStorageService { + private store: { [k: string]: string } = {}; + + get(key: string) { + return JSON.parse(this.store[key] || '{}') || {}; + } + + set(key: string, value: any): boolean { + this.store[key] = JSON.stringify(value); + return true; + } + + has(key: string): boolean { + return !!this.store[key]; + } + + remove(key: string) { + delete this.store[key]; + } + + clear() { + this.store = {}; + } +} diff --git a/front/app/src/app/shared/shared.module.ts b/front/app/src/app/shared/shared.module.ts new file mode 100644 index 00000000..a3d9c8a5 --- /dev/null +++ b/front/app/src/app/shared/shared.module.ts @@ -0,0 +1,51 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; + +import { MaterialModule } from '../material.module'; +import { MaterialExtensionsModule } from '../material-extensions.module'; + +import { FormlyModule } from '@ngx-formly/core'; +import { FormlyMaterialModule } from '@ngx-formly/material'; +import { NgProgressModule } from 'ngx-progressbar'; +import { NgProgressHttpModule } from 'ngx-progressbar/http'; +import { NgProgressRouterModule } from 'ngx-progressbar/router'; +import { NgxPermissionsModule } from 'ngx-permissions'; +import { ToastrModule } from 'ngx-toastr'; +import { TranslateModule } from '@ngx-translate/core'; + +import { BreadcrumbComponent } from './components/breadcrumb/breadcrumb.component'; +import { PageHeaderComponent } from './components/page-header/page-header.component'; +import { ErrorCodeComponent } from './components/error-code/error-code.component'; +import { DisableControlDirective } from './directives/disable-control.directive'; +import { SafeUrlPipe } from './pipes/safe-url.pipe'; +import { ToObservablePipe } from './pipes/to-observable.pipe'; + +const MODULES: any[] = [ + CommonModule, + RouterModule, + ReactiveFormsModule, + FormsModule, + MaterialModule, + MaterialExtensionsModule, + FormlyModule, + FormlyMaterialModule, + NgProgressModule, + NgProgressRouterModule, + NgProgressHttpModule, + NgxPermissionsModule, + ToastrModule, + TranslateModule, +]; +const COMPONENTS: any[] = [BreadcrumbComponent, PageHeaderComponent, ErrorCodeComponent]; +const COMPONENTS_DYNAMIC: any[] = []; +const DIRECTIVES: any[] = [DisableControlDirective]; +const PIPES: any[] = [SafeUrlPipe, ToObservablePipe]; + +@NgModule({ + imports: [...MODULES], + exports: [...MODULES, ...COMPONENTS, ...DIRECTIVES, ...PIPES], + declarations: [...COMPONENTS, ...COMPONENTS_DYNAMIC, ...DIRECTIVES, ...PIPES], +}) +export class SharedModule {} diff --git a/front/app/src/app/shared/utils/colors.ts b/front/app/src/app/shared/utils/colors.ts new file mode 100644 index 00000000..64bcbda8 --- /dev/null +++ b/front/app/src/app/shared/utils/colors.ts @@ -0,0 +1,610 @@ +export const MAT_COLORS = { + 'red': { + 50: '#FFEBEE', + 100: '#FFCDD2', + 200: '#EF9A9A', + 300: '#E57373', + 400: '#EF5350', + 500: '#F44336', + 600: '#E53935', + 700: '#D32F2F', + 800: '#C62828', + 900: '#B71C1C', + A100: '#FF8A80', + A200: '#FF5252', + A400: '#FF1744', + A700: '#D50000', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'pink': { + 50: '#FCE4EC', + 100: '#F8BBD0', + 200: '#F48FB1', + 300: '#F06292', + 400: '#EC407A', + 500: '#E91E63', + 600: '#D81B60', + 700: '#C2185B', + 800: '#AD1457', + 900: '#880E4F', + A100: '#FF80AB', + A200: '#FF4081', + A400: '#F50057', + A700: '#C51162', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'purple': { + 50: '#F3E5F5', + 100: '#E1BEE7', + 200: '#CE93D8', + 300: '#BA68C8', + 400: '#AB47BC', + 500: '#9C27B0', + 600: '#8E24AA', + 700: '#7B1FA2', + 800: '#6A1B9A', + 900: '#4A148C', + A100: '#EA80FC', + A200: '#E040FB', + A400: '#D500F9', + A700: '#AA00FF', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'deep-purple': { + 50: '#EDE7F6', + 100: '#D1C4E9', + 200: '#B39DDB', + 300: '#9575CD', + 400: '#7E57C2', + 500: '#673AB7', + 600: '#5E35B1', + 700: '#512DA8', + 800: '#4527A0', + 900: '#311B92', + A100: '#B388FF', + A200: '#7C4DFF', + A400: '#651FFF', + A700: '#6200EA', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'indigo': { + 50: '#E8EAF6', + 100: '#C5CAE9', + 200: '#9FA8DA', + 300: '#7986CB', + 400: '#5C6BC0', + 500: '#3F51B5', + 600: '#3949AB', + 700: '#303F9F', + 800: '#283593', + 900: '#1A237E', + A100: '#8C9EFF', + A200: '#536DFE', + A400: '#3D5AFE', + A700: '#304FFE', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'blue': { + 50: '#E3F2FD', + 100: '#BBDEFB', + 200: '#90CAF9', + 300: '#64B5F6', + 400: '#42A5F5', + 500: '#2196F3', + 600: '#1E88E5', + 700: '#1976D2', + 800: '#1565C0', + 900: '#0D47A1', + A100: '#82B1FF', + A200: '#448AFF', + A400: '#2979FF', + A700: '#2962FF', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'light', + A400: 'light', + A700: 'light', + }, + }, + 'light-blue': { + 50: '#E1F5FE', + 100: '#B3E5FC', + 200: '#81D4FA', + 300: '#4FC3F7', + 400: '#29B6F6', + 500: '#03A9F4', + 600: '#039BE5', + 700: '#0288D1', + 800: '#0277BD', + 900: '#01579B', + A100: '#80D8FF', + A200: '#40C4FF', + A400: '#00B0FF', + A700: '#0091EA', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'light', + }, + }, + 'cyan': { + 50: '#E0F7FA', + 100: '#B2EBF2', + 200: '#80DEEA', + 300: '#4DD0E1', + 400: '#26C6DA', + 500: '#00BCD4', + 600: '#00ACC1', + 700: '#0097A7', + 800: '#00838F', + 900: '#006064', + A100: '#84FFFF', + A200: '#18FFFF', + A400: '#00E5FF', + A700: '#00B8D4', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'teal': { + 50: '#E0F2F1', + 100: '#B2DFDB', + 200: '#80CBC4', + 300: '#4DB6AC', + 400: '#26A69A', + 500: '#009688', + 600: '#00897B', + 700: '#00796B', + 800: '#00695C', + 900: '#004D40', + A100: '#A7FFEB', + A200: '#64FFDA', + A400: '#1DE9B6', + A700: '#00BFA5', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'green': { + 50: '#E8F5E9', + 100: '#C8E6C9', + 200: '#A5D6A7', + 300: '#81C784', + 400: '#66BB6A', + 500: '#4CAF50', + 600: '#43A047', + 700: '#388E3C', + 800: '#2E7D32', + 900: '#1B5E20', + A100: '#B9F6CA', + A200: '#69F0AE', + A400: '#00E676', + A700: '#00C853', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'light-green': { + 50: '#F1F8E9', + 100: '#DCEDC8', + 200: '#C5E1A5', + 300: '#AED581', + 400: '#9CCC65', + 500: '#8BC34A', + 600: '#7CB342', + 700: '#689F38', + 800: '#558B2F', + 900: '#33691E', + A100: '#CCFF90', + A200: '#B2FF59', + A400: '#76FF03', + A700: '#64DD17', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'lime': { + 50: '#F9FBE7', + 100: '#F0F4C3', + 200: '#E6EE9C', + 300: '#DCE775', + 400: '#D4E157', + 500: '#CDDC39', + 600: '#C0CA33', + 700: '#AFB42B', + 800: '#9E9D24', + 900: '#827717', + A100: '#F4FF81', + A200: '#EEFF41', + A400: '#C6FF00', + A700: '#AEEA00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'yellow': { + 50: '#FFFDE7', + 100: '#FFF9C4', + 200: '#FFF59D', + 300: '#FFF176', + 400: '#FFEE58', + 500: '#FFEB3B', + 600: '#FDD835', + 700: '#FBC02D', + 800: '#F9A825', + 900: '#F57F17', + A100: '#FFFF8D', + A200: '#FFFF00', + A400: '#FFEA00', + A700: '#FFD600', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'dark', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'amber': { + 50: '#FFF8E1', + 100: '#FFECB3', + 200: '#FFE082', + 300: '#FFD54F', + 400: '#FFCA28', + 500: '#FFC107', + 600: '#FFB300', + 700: '#FFA000', + 800: '#FF8F00', + 900: '#FF6F00', + A100: '#FFE57F', + A200: '#FFD740', + A400: '#FFC400', + A700: '#FFAB00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'dark', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'orange': { + 50: '#FFF3E0', + 100: '#FFE0B2', + 200: '#FFCC80', + 300: '#FFB74D', + 400: '#FFA726', + 500: '#FF9800', + 600: '#FB8C00', + 700: '#F57C00', + 800: '#EF6C00', + 900: '#E65100', + A100: '#FFD180', + A200: '#FFAB40', + A400: '#FF9100', + A700: '#FF6D00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'deep-orange': { + 50: '#FBE9E7', + 100: '#FFCCBC', + 200: '#FFAB91', + 300: '#FF8A65', + 400: '#FF7043', + 500: '#FF5722', + 600: '#F4511E', + 700: '#E64A19', + 800: '#D84315', + 900: '#BF360C', + A100: '#FF9E80', + A200: '#FF6E40', + A400: '#FF3D00', + A700: '#DD2C00', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'dark', + 700: 'dark', + 800: 'dark', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'dark', + }, + }, + 'brown': { + 50: '#EFEBE9', + 100: '#D7CCC8', + 200: '#BCAAA4', + 300: '#A1887F', + 400: '#8D6E63', + 500: '#795548', + 600: '#6D4C41', + 700: '#5D4037', + 800: '#4E342E', + 900: '#3E2723', + A100: '#D7CCC8', + A200: '#BCAAA4', + A400: '#8D6E63', + A700: '#5D4037', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'light', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'light', + A700: 'light', + }, + }, + 'gray': { + 50: '#FAFAFA', + 100: '#F5F5F5', + 200: '#EEEEEE', + 300: '#E0E0E0', + 400: '#BDBDBD', + 500: '#9E9E9E', + 600: '#757575', + 700: '#616161', + 800: '#424242', + 900: '#212121', + A100: '#FFFFFF', + A200: ' #EEEEEE', + A400: '#BDBDBD', + A700: '#616161', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'dark', + 500: 'dark', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'dark', + A700: 'light', + }, + }, + 'blue-gray': { + 50: '#ECEFF1', + 100: '#CFD8DC', + 200: '#B0BEC5', + 300: '#90A4AE', + 400: '#78909C', + 500: '#607D8B', + 600: '#546E7A', + 700: '#455A64', + 800: '#37474F', + 900: '#263238', + A100: '#CFD8DC', + A200: '#B0BEC5', + A400: '#78909C', + A700: '#455A64', + contrast: { + 50: 'dark', + 100: 'dark', + 200: 'dark', + 300: 'dark', + 400: 'light', + 500: 'light', + 600: 'light', + 700: 'light', + 800: 'light', + 900: 'light', + A100: 'dark', + A200: 'dark', + A400: 'light', + A700: 'light', + }, + }, +}; diff --git a/front/app/src/app/shared/utils/icons.ts b/front/app/src/app/shared/utils/icons.ts new file mode 100644 index 00000000..afee22f1 --- /dev/null +++ b/front/app/src/app/shared/utils/icons.ts @@ -0,0 +1,1072 @@ +export const MAT_ICONS = { + action: [ + '3d_rotation', + 'accessibility', + 'accessibility_new', + 'accessible', + 'accessible_forward', + 'account_balance', + 'account_balance_wallet', + 'account_box', + 'account_circle', + 'add_shopping_cart', + 'alarm', + 'alarm_add', + 'alarm_off', + 'alarm_on', + 'all_inbox', + 'all_out', + 'android', + 'announcement', + 'arrow_right_alt', + 'aspect_ratio', + 'assessment', + 'assignment', + 'assignment_ind', + 'assignment_late', + 'assignment_return', + 'assignment_returned', + 'assignment_turned_in', + 'autorenew', + 'backup', + 'book', + 'bookmark', + 'bookmark_border', + 'bookmarks', + 'bug_report', + 'build', + 'cached', + 'calendar_today', + 'calendar_view_day', + 'camera_enhance', + 'card_giftcard', + 'card_membership', + 'card_travel', + 'change_history', + 'check_circle', + 'check_circle_outline', + 'chrome_reader_mode', + 'class', + 'code', + 'commute', + 'compare_arrows', + 'contact_support', + 'copyright', + 'credit_card', + 'dashboard', + 'date_range', + 'delete', + 'delete_forever', + 'delete_outline', + 'description', + 'dns', + 'done', + 'done_all', + 'done_outline', + 'donut_large', + 'donut_small', + 'drag_indicator', + 'eject', + 'euro_symbol', + 'event', + 'event_seat', + 'exit_to_app', + 'explore', + 'explore_off', + 'extension', + 'face', + 'favorite', + 'favorite_border', + 'feedback', + 'find_in_page', + 'find_replace', + 'fingerprint', + 'flight_land', + 'flight_takeoff', + 'flip_to_back', + 'flip_to_front', + 'g_translate', + 'gavel', + 'get_app', + 'gif', + 'grade', + 'group_work', + 'help', + 'help_outline', + 'highlight_off', + 'history', + 'home', + 'horizontal_split', + 'hourglass_empty', + 'hourglass_full', + 'http', + 'https', + 'important_devices', + 'info', + 'input', + 'invert_colors', + 'label', + 'label_important', + 'label_off', + 'language', + 'launch', + 'line_style', + 'line_weight', + 'list', + 'lock', + 'lock_open', + 'loyalty', + 'markunread_mailbox', + 'maximize', + 'minimize', + 'motorcycle', + 'note_add', + 'offline_bolt', + 'offline_pin', + 'opacity', + 'open_in_browser', + 'open_in_new', + 'open_with', + 'pageview', + 'pan_tool', + 'payment', + 'perm_camera_mic', + 'perm_contact_calendar', + 'perm_data_setting', + 'perm_device_information', + 'perm_identity', + 'perm_media', + 'perm_phone_msg', + 'perm_scan_wifi', + 'pets', + 'picture_in_picture', + 'picture_in_picture_alt', + 'play_for_work', + 'polymer', + 'power_settings_new', + 'pregnant_woman', + 'print', + 'query_builder', + 'question_answer', + 'receipt', + 'record_voice_over', + 'redeem', + 'remove_shopping_cart', + 'reorder', + 'report_problem', + 'restore', + 'restore_from_trash', + 'restore_page', + 'room', + 'rounded_corner', + 'rowing', + 'schedule', + 'search', + 'settings', + 'settings_applications', + 'settings_backup_restore', + 'settings_bluetooth', + 'settings_brightness', + 'settings_cell', + 'settings_ethernet', + 'settings_input_antenna', + 'settings_input_component', + 'settings_input_composite', + 'settings_input_hdmi', + 'settings_input_svideo', + 'settings_overscan', + 'settings_phone', + 'settings_power', + 'settings_remote', + 'settings_voice', + 'shop', + 'shop_two', + 'shopping_basket', + 'shopping_cart', + 'speaker_notes', + 'speaker_notes_off', + 'spellcheck', + 'star_rate', + 'stars', + 'store', + 'subject', + 'supervised_user_circle', + 'supervisor_account', + 'swap_horiz', + 'swap_horizontal_circle', + 'swap_vert', + 'swap_vertical_circle', + 'tab', + 'tab_unselected', + 'text_rotate_up', + 'text_rotate_vertical', + 'text_rotation_down', + 'text_rotation_none', + 'theaters', + 'thumb_down', + 'thumb_up', + 'thumbs_up_down', + 'timeline', + 'toc', + 'today', + 'toll', + 'touch_app', + 'track_changes', + 'translate', + 'trending_down', + 'trending_flat', + 'trending_up', + 'turned_in', + 'turned_in_not', + 'update', + 'verified_user', + 'vertical_split', + 'view_agenda', + 'view_array', + 'view_carousel', + 'view_column', + 'view_day', + 'view_headline', + 'view_list', + 'view_module', + 'view_quilt', + 'view_stream', + 'view_week', + 'visibility', + 'visibility_off', + 'voice_over_off', + 'watch_later', + 'work', + 'work_off', + 'work_outline', + 'youtube_searched_for', + 'zoom_in', + 'zoom_out', + ], + alert: ['add_alert', 'error', 'error_outline', 'notification_important', 'warning'], + av: [ + '4k', + 'add_to_queue', + 'airplay', + 'album', + 'art_track', + 'av_timer', + 'branding_watermark', + 'call_to_action', + 'closed_caption', + 'control_camera', + 'equalizer', + 'explicit', + 'fast_forward', + 'fast_rewind', + 'featured_play_list', + 'featured_video', + 'fiber_dvr', + 'fiber_manual_record', + 'fiber_new', + 'fiber_pin', + 'fiber_smart_record', + 'forward_10', + 'forward_30', + 'forward_5', + 'games', + 'hd', + 'hearing', + 'high_quality', + 'library_add', + 'library_books', + 'library_music', + 'loop', + 'mic', + 'mic_none', + 'mic_off', + 'missed_video_call', + 'movie', + 'music_video', + 'new_releases', + 'not_interested', + 'note', + 'pause', + 'pause_circle_filled', + 'pause_circle_outline', + 'play_arrow', + 'play_circle_filled', + 'play_circle_filled_white', + 'play_circle_outline', + 'playlist_add', + 'playlist_add_check', + 'playlist_play', + 'queue', + 'queue_music', + 'queue_play_next', + 'radio', + 'recent_actors', + 'remove_from_queue', + 'repeat', + 'repeat_one', + 'replay', + 'replay_10', + 'replay_30', + 'replay_5', + 'shuffle', + 'skip_next', + 'skip_previous', + 'slow_motion_video', + 'snooze', + 'sort_by_alpha', + 'stop', + 'subscriptions', + 'subtitles', + 'surround_sound', + 'video_call', + 'video_label', + 'video_library', + 'videocam', + 'videocam_off', + 'volume_down', + 'volume_mute', + 'volume_off', + 'volume_up', + 'web', + 'web_asset', + ], + communication: [ + 'alternate_email', + 'business', + 'call', + 'call_end', + 'call_made', + 'call_merge', + 'call_missed', + 'call_missed_outgoing', + 'call_received', + 'call_split', + 'cancel_presentation', + 'cell_wifi', + 'chat', + 'chat_bubble', + 'chat_bubble_outline', + 'clear_all', + 'comment', + 'contact_mail', + 'contact_phone', + 'contacts', + 'desktop_access_disabled', + 'dialer_sip', + 'dialpad', + 'domain_disabled', + 'duo', + 'email', + 'forum', + 'import_contacts', + 'import_export', + 'invert_colors_off', + 'list_alt', + 'live_help', + 'location_off', + 'location_on', + 'mail_outline', + 'message', + 'mobile_screen_share', + 'no_sim', + 'pause_presentation', + 'person_add_disabled', + 'phone', + 'phonelink_erase', + 'phonelink_lock', + 'phonelink_ring', + 'phonelink_setup', + 'portable_wifi_off', + 'present_to_all', + 'print_disabled', + 'ring_volume', + 'rss_feed', + 'screen_share', + 'sentiment_satisfied_alt', + 'speaker_phone', + 'stay_current_landscape', + 'stay_current_portrait', + 'stay_primary_landscape', + 'stay_primary_portrait', + 'stop_screen_share', + 'swap_calls', + 'textsms', + 'unsubscribe', + 'voicemail', + 'vpn_key', + ], + content: [ + 'add', + 'add_box', + 'add_circle', + 'add_circle_outline', + 'archive', + 'backspace', + 'ballot', + 'block', + 'clear', + 'create', + 'delete_sweep', + 'drafts', + 'file_copy', + 'filter_list', + 'flag', + 'font_download', + 'forward', + 'gesture', + 'how_to_reg', + 'how_to_vote', + 'inbox', + 'link', + 'link_off', + 'low_priority', + 'mail', + 'markunread', + 'move_to_inbox', + 'next_week', + 'outlined_flag', + 'redo', + 'remove', + 'remove_circle', + 'remove_circle_outline', + 'reply', + 'reply_all', + 'report', + 'report_off', + 'save', + 'save_alt', + 'select_all', + 'send', + 'sort', + 'text_format', + 'unarchive', + 'undo', + 'waves', + 'weekend', + 'where_to_vote', + ], + device: [ + 'access_alarm', + 'access_alarms', + 'access_time', + 'add_alarm', + 'add_to_home_screen', + 'airplanemode_active', + 'airplanemode_inactive', + // 'battery_20', + // 'battery_30', + // 'battery_50', + // 'battery_60', + // 'battery_80', + // 'battery_90', + 'battery_alert', + // 'battery_charging_20', + // 'battery_charging_30', + // 'battery_charging_50', + // 'battery_charging_60', + // 'battery_charging_80', + // 'battery_charging_90', + 'battery_charging_full', + 'battery_full', + 'battery_std', + 'battery_unknown', + 'bluetooth', + 'bluetooth_connected', + 'bluetooth_disabled', + 'bluetooth_searching', + 'brightness_auto', + 'brightness_high', + 'brightness_low', + 'brightness_medium', + 'data_usage', + 'developer_mode', + 'devices', + 'dvr', + 'gps_fixed', + 'gps_not_fixed', + 'gps_off', + 'graphic_eq', + 'location_disabled', + 'location_searching', + 'mobile_friendly', + 'mobile_off', + // 'network_cell', + // 'network_wifi', + 'nfc', + 'screen_lock_landscape', + 'screen_lock_portrait', + 'screen_lock_rotation', + 'screen_rotation', + 'sd_storage', + 'settings_system_daydream', + // 'signal_cellular_0_bar', + // 'signal_cellular_1_bar', + // 'signal_cellular_2_bar', + // 'signal_cellular_3_bar', + // 'signal_cellular_4_bar', + 'signal_cellular_alt', + // 'signal_cellular_connected_no_internet_0_bar', + // 'signal_cellular_connected_no_internet_1_bar', + // 'signal_cellular_connected_no_internet_2_bar', + // 'signal_cellular_connected_no_internet_3_bar', + 'signal_cellular_connected_no_internet_4_bar', + 'signal_cellular_no_sim', + 'signal_cellular_null', + 'signal_cellular_off', + // 'signal_wifi_0_bar', + // 'signal_wifi_1_bar', + // 'signal_wifi_1_bar_lock', + // 'signal_wifi_2_bar', + // 'signal_wifi_2_bar_lock', + // 'signal_wifi_3_bar', + // 'signal_wifi_3_bar_lock', + 'signal_wifi_4_bar', + 'signal_wifi_4_bar_lock', + 'signal_wifi_off', + 'storage', + 'usb', + 'wallpaper', + 'widgets', + 'wifi_lock', + 'wifi_tethering', + ], + editor: [ + 'add_comment', + 'attach_file', + 'attach_money', + 'bar_chart', + 'border_all', + 'border_bottom', + 'border_clear', + 'border_color', + 'border_horizontal', + 'border_inner', + 'border_left', + 'border_outer', + 'border_right', + 'border_style', + 'border_top', + 'border_vertical', + 'bubble_chart', + 'drag_handle', + 'format_align_center', + 'format_align_justify', + 'format_align_left', + 'format_align_right', + 'format_bold', + 'format_clear', + 'format_color_fill', + 'format_color_reset', + 'format_color_text', + 'format_indent_decrease', + 'format_indent_increase', + 'format_italic', + 'format_line_spacing', + 'format_list_bulleted', + 'format_list_numbered', + 'format_list_numbered_rtl', + 'format_paint', + 'format_quote', + 'format_shapes', + 'format_size', + 'format_strikethrough', + 'format_textdirection_l_to_r', + 'format_textdirection_r_to_l', + 'format_underlined', + 'functions', + 'highlight', + 'insert_chart', + 'insert_chart_outlined', + 'insert_comment', + 'insert_drive_file', + 'insert_emoticon', + 'insert_invitation', + 'insert_link', + 'insert_photo', + 'linear_scale', + 'merge_type', + 'mode_comment', + 'monetization_on', + 'money_off', + 'multiline_chart', + 'notes', + 'pie_chart', + 'publish', + 'scatter_plot', + 'score', + 'short_text', + 'show_chart', + 'space_bar', + 'strikethrough_s', + 'table_chart', + 'text_fields', + 'title', + 'vertical_align_bottom', + 'vertical_align_center', + 'vertical_align_top', + 'wrap_text', + ], + file: [ + 'attachment', + 'cloud', + 'cloud_circle', + 'cloud_done', + 'cloud_download', + 'cloud_off', + 'cloud_queue', + 'cloud_upload', + 'create_new_folder', + 'folder', + 'folder_open', + 'folder_shared', + ], + hardware: [ + 'cast', + 'cast_connected', + 'cast_for_education', + 'computer', + 'desktop_mac', + 'desktop_windows', + 'developer_board', + 'device_hub', + 'device_unknown', + 'devices_other', + 'dock', + 'gamepad', + 'headset', + 'headset_mic', + 'keyboard', + 'keyboard_arrow_down', + 'keyboard_arrow_left', + 'keyboard_arrow_right', + 'keyboard_arrow_up', + 'keyboard_backspace', + 'keyboard_capslock', + 'keyboard_hide', + 'keyboard_return', + 'keyboard_tab', + 'keyboard_voice', + 'laptop', + 'laptop_chromebook', + 'laptop_mac', + 'laptop_windows', + 'memory', + 'mouse', + 'phone_android', + 'phone_iphone', + 'phonelink', + 'phonelink_off', + 'power_input', + 'router', + 'scanner', + 'security', + 'sim_card', + 'smartphone', + 'speaker', + 'speaker_group', + 'tablet', + 'tablet_android', + 'tablet_mac', + 'toys', + 'tv', + 'videogame_asset', + 'watch', + ], + image: [ + 'add_a_photo', + 'add_photo_alternate', + 'add_to_photos', + 'adjust', + 'assistant', + 'assistant_photo', + 'audiotrack', + 'blur_circular', + 'blur_linear', + 'blur_off', + 'blur_on', + 'brightness_1', + 'brightness_2', + 'brightness_3', + 'brightness_4', + 'brightness_5', + 'brightness_6', + 'brightness_7', + 'broken_image', + 'brush', + 'burst_mode', + 'camera', + 'camera_alt', + 'camera_front', + 'camera_rear', + 'camera_roll', + 'center_focus_strong', + 'center_focus_weak', + 'collections', + 'collections_bookmark', + 'color_lens', + 'colorize', + 'compare', + 'control_point', + 'control_point_duplicate', + 'crop', + 'crop_16_9', + 'crop_3_2', + 'crop_5_4', + 'crop_7_5', + 'crop_din', + 'crop_free', + 'crop_landscape', + 'crop_original', + 'crop_portrait', + 'crop_rotate', + 'crop_square', + 'dehaze', + 'details', + 'edit', + 'exposure', + 'exposure_neg_1', + 'exposure_neg_2', + 'exposure_plus_1', + 'exposure_plus_2', + 'exposure_zero', + 'filter', + 'filter_1', + 'filter_2', + 'filter_3', + 'filter_4', + 'filter_5', + 'filter_6', + 'filter_7', + 'filter_8', + 'filter_9', + 'filter_9_plus', + 'filter_b_and_w', + 'filter_center_focus', + 'filter_drama', + 'filter_frames', + 'filter_hdr', + 'filter_none', + 'filter_tilt_shift', + 'filter_vintage', + 'flare', + 'flash_auto', + 'flash_off', + 'flash_on', + 'flip', + 'gradient', + 'grain', + 'grid_off', + 'grid_on', + 'hdr_off', + 'hdr_on', + 'hdr_strong', + 'hdr_weak', + 'healing', + 'image', + 'image_aspect_ratio', + 'image_search', + 'iso', + 'landscape', + 'leak_add', + 'leak_remove', + 'lens', + 'linked_camera', + 'looks', + 'looks_3', + 'looks_4', + 'looks_5', + 'looks_6', + 'looks_one', + 'looks_two', + 'loupe', + 'monochrome_photos', + 'movie_creation', + 'movie_filter', + 'music_note', + 'music_off', + 'nature', + 'nature_people', + 'navigate_before', + 'navigate_next', + 'palette', + 'panorama', + 'panorama_fish_eye', + 'panorama_horizontal', + 'panorama_vertical', + 'panorama_wide_angle', + 'photo', + 'photo_album', + 'photo_camera', + 'photo_filter', + 'photo_library', + 'photo_size_select_actual', + 'photo_size_select_large', + 'photo_size_select_small', + 'picture_as_pdf', + 'portrait', + 'remove_red_eye', + 'rotate_90_degrees_ccw', + 'rotate_left', + 'rotate_right', + 'shutter_speed', + 'slideshow', + 'straighten', + 'style', + 'switch_camera', + 'switch_video', + 'tag_faces', + 'texture', + 'timelapse', + 'timer', + 'timer_10', + 'timer_3', + 'timer_off', + 'tonality', + 'transform', + 'tune', + 'view_comfy', + 'view_compact', + 'vignette', + 'wb_auto', + 'wb_cloudy', + 'wb_incandescent', + 'wb_iridescent', + 'wb_sunny', + ], + maps: [ + '360', + 'add_location', + 'atm', + 'beenhere', + 'category', + 'compass_calibration', + 'departure_board', + 'directions', + 'directions_bike', + 'directions_boat', + 'directions_bus', + 'directions_car', + 'directions_railway', + 'directions_run', + 'directions_subway', + 'directions_transit', + 'directions_walk', + 'edit_attributes', + 'edit_location', + 'ev_station', + 'fastfood', + 'flight', + 'hotel', + 'layers', + 'layers_clear', + 'local_activity', + 'local_airport', + 'local_atm', + 'local_bar', + 'local_cafe', + 'local_car_wash', + 'local_convenience_store', + 'local_dining', + 'local_drink', + 'local_florist', + 'local_gas_station', + 'local_grocery_store', + 'local_hospital', + 'local_hotel', + 'local_laundry_service', + 'local_library', + 'local_mall', + 'local_movies', + 'local_offer', + 'local_parking', + 'local_pharmacy', + 'local_phone', + 'local_pizza', + 'local_play', + 'local_post_office', + 'local_printshop', + 'local_see', + 'local_shipping', + 'local_taxi', + 'map', + 'money', + 'my_location', + 'navigation', + 'near_me', + 'not_listed_location', + 'person_pin', + 'person_pin_circle', + 'pin_drop', + 'place', + 'rate_review', + 'restaurant', + 'restaurant_menu', + 'satellite', + 'store_mall_directory', + 'streetview', + 'subway', + 'terrain', + 'traffic', + 'train', + 'tram', + 'transfer_within_a_station', + 'transit_enterexit', + 'trip_origin', + 'zoom_out_map', + ], + navigation: [ + 'apps', + 'arrow_back', + 'arrow_back_ios', + 'arrow_downward', + 'arrow_drop_down', + 'arrow_drop_down_circle', + 'arrow_drop_up', + 'arrow_forward', + 'arrow_forward_ios', + 'arrow_left', + 'arrow_right', + 'arrow_upward', + 'cancel', + 'check', + 'chevron_left', + 'chevron_right', + 'close', + 'expand_less', + 'expand_more', + 'first_page', + 'fullscreen', + 'fullscreen_exit', + 'last_page', + 'menu', + 'more_horiz', + 'more_vert', + 'refresh', + 'subdirectory_arrow_left', + 'subdirectory_arrow_right', + 'unfold_less', + 'unfold_more', + ], + notification: [ + 'adb', + 'airline_seat_flat', + 'airline_seat_flat_angled', + 'airline_seat_individual_suite', + 'airline_seat_legroom_extra', + 'airline_seat_legroom_normal', + 'airline_seat_legroom_reduced', + 'airline_seat_recline_extra', + 'airline_seat_recline_normal', + 'bluetooth_audio', + 'confirmation_number', + 'disc_full', + 'drive_eta', + 'enhanced_encryption', + 'event_available', + 'event_busy', + 'event_note', + 'folder_special', + 'live_tv', + 'mms', + 'more', + 'network_check', + 'network_locked', + 'no_encryption', + 'ondemand_video', + 'personal_video', + 'phone_bluetooth_speaker', + 'phone_callback', + 'phone_forwarded', + 'phone_in_talk', + 'phone_locked', + 'phone_missed', + 'phone_paused', + 'power', + 'power_off', + 'priority_high', + 'sd_card', + 'sms', + 'sms_failed', + 'sync', + 'sync_disabled', + 'sync_problem', + 'system_update', + 'tap_and_play', + 'time_to_leave', + 'tv_off', + 'vibration', + 'voice_chat', + 'vpn_lock', + 'wc', + 'wifi', + 'wifi_off', + ], + places: [ + 'ac_unit', + 'airport_shuttle', + 'all_inclusive', + 'beach_access', + 'business_center', + 'casino', + 'child_care', + 'child_friendly', + 'fitness_center', + 'free_breakfast', + 'golf_course', + 'hot_tub', + 'kitchen', + 'meeting_room', + 'no_meeting_room', + 'pool', + 'room_service', + 'rv_hookup', + 'smoke_free', + 'smoking_rooms', + 'spa', + ], + social: [ + 'cake', + 'domain', + 'group', + 'group_add', + 'location_city', + 'mood', + 'mood_bad', + 'notifications', + 'notifications_active', + 'notifications_none', + 'notifications_off', + 'notifications_paused', + 'pages', + 'party_mode', + 'people', + 'people_outline', + 'person', + 'person_add', + 'person_outline', + 'plus_one', + 'poll', + 'public', + 'school', + 'sentiment_dissatisfied', + 'sentiment_satisfied', + 'sentiment_very_dissatisfied', + 'sentiment_very_satisfied', + 'share', + 'thumb_down_alt', + 'thumb_up_alt', + 'whatshot', + ], + toggle: [ + 'check_box', + 'check_box_outline_blank', + 'indeterminate_check_box', + 'radio_button_checked', + 'radio_button_unchecked', + 'star', + 'star_border', + 'star_half', + 'toggle_off', + 'toggle_on', + ], +}; diff --git a/front/app/src/app/theme/admin-layout/admin-layout.component.html b/front/app/src/app/theme/admin-layout/admin-layout.component.html new file mode 100644 index 00000000..3114f76f --- /dev/null +++ b/front/app/src/app/theme/admin-layout/admin-layout.component.html @@ -0,0 +1,34 @@ + +
+ + + + + + + + + + + + + + + + +
+ +
+
+
+
diff --git a/front/app/src/app/theme/admin-layout/admin-layout.component.scss b/front/app/src/app/theme/admin-layout/admin-layout.component.scss new file mode 100644 index 00000000..dae9889f --- /dev/null +++ b/front/app/src/app/theme/admin-layout/admin-layout.component.scss @@ -0,0 +1,160 @@ +@use '@angular/material' as mat; +@use '../style/variables'; +@use '../style/transitions'; +@use '../style/breakpoints'; + +.matero-container-wrap, +.matero-container { + height: 100%; +} + +.matero-content { + position: relative; + padding: variables.$gutter; +} + +.matero-sidenav { + position: absolute; + overflow-x: hidden; + transition: transitions.swift-ease-out(width); // Only set width property + + @include mat.elevation(2); + + &.mat-drawer-side { + border-width: 0; + + [dir='rtl'] & { + border-width: 0; + } + } +} + +// Layout control +.matero-header-above { + .matero-container { + height: calc(100% - #{variables.$toolbar-height-desktop}) !important; + } + + .matero-sidebar-main { + height: 100% !important; + } +} + +// Layout control +.matero-sidenav-collapsed, +.matero-sidenav-collapsed-fix { + .matero-sidenav { + width: variables.$sidenav-collapsed-width; + + .menu-name, + .menu-label, + .menu-badge, + .menu-caret, + .matero-user-panel-name, + .matero-user-panel-email, + .matero-user-panel-icons { + opacity: 0; + } + + .matero-user-panel-avatar { + transform: scale(.5); + } + + &:hover { + width: variables.$sidenav-width; + + .menu-name, + .menu-label, + .menu-badge, + .menu-caret, + .matero-user-panel-name, + .matero-user-panel-email, + .matero-user-panel-icons { + opacity: 1; + } + + .matero-user-panel-avatar { + transform: scale(1); + } + } + } +} + +// Layout control +.matero-sidenav-collapsed { + .matero-content-wrap { + margin-left: variables.$sidenav-collapsed-width !important; + + [dir='rtl'] & { + margin-right: variables.$sidenav-collapsed-width !important; + margin-left: auto !important; + } + } + + &[dir='rtl'] .matero-content-wrap { + margin-right: variables.$sidenav-collapsed-width !important; + margin-left: auto !important; + } +} + +// Layout control +.matero-navbar-top { + .matero-topmenu { + top: 0; + } + + .matero-branding { + margin-left: 16px; + + [dir='rtl'] & { + margin-right: 16px; + margin-left: auto; + } + } +} + +// Layout control +.matero-header-fixed { + .matero-header { + position: sticky; + top: 0; + } + + .matero-topmenu { + top: variables.$topmenu-sticky-position-desktop; + + @include breakpoints.bp-lt(small) { + & { + top: variables.$topmenu-sticky-position-mobile; + } + } + } + + &.matero-navbar-side { + .matero-toolbar { + border-bottom: unset; + + @include mat.elevation(2); + } + } +} + +// Fix the init content width +.matero-content-width-fix { + .matero-content-wrap { + margin-left: variables.$sidenav-width !important; + + [dir='rtl'] & { + margin-right: variables.$sidenav-width !important; + margin-left: auto !important; + } + } +} + +// Colorful +.matero-header-white { + .matero-toolbar, + .matero-topmenu { + background-color: white; + } +} diff --git a/front/app/src/app/theme/admin-layout/admin-layout.component.ts b/front/app/src/app/theme/admin-layout/admin-layout.component.ts new file mode 100644 index 00000000..8704c5fa --- /dev/null +++ b/front/app/src/app/theme/admin-layout/admin-layout.component.ts @@ -0,0 +1,163 @@ +import { + Component, + OnDestroy, + ViewChild, + HostBinding, + Inject, + Optional, + ViewEncapsulation, +} from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { NavigationEnd, Router } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { BreakpointObserver, MediaMatcher } from '@angular/cdk/layout'; +import { Directionality } from '@angular/cdk/bidi'; +import { MatSidenav, MatSidenavContent } from '@angular/material/sidenav'; + +import { SettingsService, AppSettings } from '@core'; +import { AppDirectionality } from '@shared'; + +const MOBILE_MEDIAQUERY = 'screen and (max-width: 599px)'; +const TABLET_MEDIAQUERY = 'screen and (min-width: 600px) and (max-width: 959px)'; +const MONITOR_MEDIAQUERY = 'screen and (min-width: 960px)'; + +@Component({ + selector: 'app-admin-layout', + templateUrl: './admin-layout.component.html', + styleUrls: ['./admin-layout.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class AdminLayoutComponent implements OnDestroy { + @ViewChild('sidenav', { static: true }) sidenav!: MatSidenav; + @ViewChild('content', { static: true }) content!: MatSidenavContent; + + options = this.settings.getOptions(); + + private layoutChangesSubscription = Subscription.EMPTY; + + get isOver(): boolean { + return this.isMobileScreen; + } + + private isMobileScreen = false; + + @HostBinding('class.matero-content-width-fix') get contentWidthFix() { + return ( + this.isContentWidthFixed && + this.options.navPos === 'side' && + this.options.sidenavOpened && + !this.isOver + ); + } + + private isContentWidthFixed = true; + + @HostBinding('class.matero-sidenav-collapsed-fix') get collapsedWidthFix() { + return ( + this.isCollapsedWidthFixed && + (this.options.navPos === 'top' || (this.options.sidenavOpened && this.isOver)) + ); + } + + private isCollapsedWidthFixed = false; + + private htmlElement!: HTMLHtmlElement; + + constructor( + private router: Router, + private mediaMatcher: MediaMatcher, + private breakpointObserver: BreakpointObserver, + private settings: SettingsService, + @Optional() @Inject(DOCUMENT) private document: Document, + @Inject(Directionality) public dir: AppDirectionality + ) { + this.dir.value = this.options.dir; + this.document.body.dir = this.dir.value; + + this.htmlElement = this.document.querySelector('html')!; + + this.layoutChangesSubscription = this.breakpointObserver + .observe([MOBILE_MEDIAQUERY, TABLET_MEDIAQUERY, MONITOR_MEDIAQUERY]) + .subscribe(state => { + // SidenavOpened must be reset true when layout changes + this.options.sidenavOpened = true; + + this.isMobileScreen = state.breakpoints[MOBILE_MEDIAQUERY]; + this.options.sidenavCollapsed = state.breakpoints[TABLET_MEDIAQUERY]; + this.isContentWidthFixed = state.breakpoints[MONITOR_MEDIAQUERY]; + }); + + this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(e => { + if (this.isOver) { + this.sidenav.close(); + } + this.content.scrollTo({ top: 0 }); + }); + + if (this.options.theme === 'auto') { + this.setAutoTheme(); + } + + // Initialize project theme with options + this.receiveOptions(this.options); + } + + ngOnDestroy() { + this.layoutChangesSubscription.unsubscribe(); + } + + toggleCollapsed() { + this.isContentWidthFixed = false; + this.options.sidenavCollapsed = !this.options.sidenavCollapsed; + this.resetCollapsedState(); + } + + // TODO: Trigger when transition end + resetCollapsedState(timer = 400) { + setTimeout(() => this.settings.setOptions(this.options), timer); + } + + onSidenavClosedStart() { + this.isContentWidthFixed = false; + } + + onSidenavOpenedChange(isOpened: boolean) { + this.isCollapsedWidthFixed = !this.isOver; + this.options.sidenavOpened = isOpened; + this.settings.setOptions(this.options); + } + + setAutoTheme() { + // Check whether the browser support `prefers-color-scheme` + if (this.mediaMatcher.matchMedia('(prefers-color-scheme)').media !== 'not all') { + const isSystemDark = this.mediaMatcher.matchMedia('(prefers-color-scheme: dark)').matches; + // Set theme to dark if `prefers-color-scheme` is dark. Otherwise, set it to light. + this.options.theme = isSystemDark ? 'dark' : 'light'; + } else { + // If the browser does not support `prefers-color-scheme`, set the default to light. + this.options.theme = 'light'; + } + } + + // Demo purposes only + + receiveOptions(options: AppSettings): void { + this.options = options; + this.toggleDarkTheme(options); + this.toggleDirection(options); + } + + toggleDarkTheme(options: AppSettings) { + if (options.theme === 'dark') { + this.htmlElement.classList.add('theme-dark'); + } else { + this.htmlElement.classList.remove('theme-dark'); + } + } + + toggleDirection(options: AppSettings) { + this.dir.value = options.dir; + this.document.body.dir = this.dir.value; + } +} diff --git a/front/app/src/app/theme/auth-layout/auth-layout.component.html b/front/app/src/app/theme/auth-layout/auth-layout.component.html new file mode 100644 index 00000000..94d80c77 --- /dev/null +++ b/front/app/src/app/theme/auth-layout/auth-layout.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/front/app/src/app/theme/auth-layout/auth-layout.component.scss b/front/app/src/app/theme/auth-layout/auth-layout.component.scss new file mode 100644 index 00000000..8085a917 --- /dev/null +++ b/front/app/src/app/theme/auth-layout/auth-layout.component.scss @@ -0,0 +1,26 @@ +.matero-auth-container { + position: relative; + display: flex; + justify-content: center; + min-height: 100%; + padding: 16px; + background-color: #212121; + background-image: + linear-gradient( + 45deg, + rgba(255, 255, 255, .05) 25%, + transparent 25%, + transparent 75%, + rgba(255, 255, 255, .05) 75%, + rgba(255, 255, 255, .05) + ), + linear-gradient( + -45deg, + rgba(255, 255, 255, .05) 25%, + transparent 25%, + transparent 75%, + rgba(255, 255, 255, .05) 75%, + rgba(255, 255, 255, .05) + ); + background-size: 60px 60px; +} diff --git a/front/app/src/app/theme/auth-layout/auth-layout.component.ts b/front/app/src/app/theme/auth-layout/auth-layout.component.ts new file mode 100644 index 00000000..bec0a8f1 --- /dev/null +++ b/front/app/src/app/theme/auth-layout/auth-layout.component.ts @@ -0,0 +1,9 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'app-auth-layout', + templateUrl: './auth-layout.component.html', + styleUrls: ['./auth-layout.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class AuthLayoutComponent {} diff --git a/front/app/src/app/theme/header/_header-theme.scss b/front/app/src/app/theme/header/_header-theme.scss new file mode 100644 index 00000000..41e98c5f --- /dev/null +++ b/front/app/src/app/theme/header/_header-theme.scss @@ -0,0 +1,11 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-toolbar { + border-bottom: 1px solid mat.get-color-from-palette($foreground, divider); + } +} diff --git a/front/app/src/app/theme/header/header.component.html b/front/app/src/app/theme/header/header.component.html new file mode 100644 index 00000000..34c00332 --- /dev/null +++ b/front/app/src/app/theme/header/header.component.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/front/app/src/app/theme/header/header.component.scss b/front/app/src/app/theme/header/header.component.scss new file mode 100644 index 00000000..5a5a2e91 --- /dev/null +++ b/front/app/src/app/theme/header/header.component.scss @@ -0,0 +1,8 @@ +.matero-header { + position: relative; + z-index: 200; + + .matero-toolbar { + padding: 0 8px; + } +} diff --git a/front/app/src/app/theme/header/header.component.ts b/front/app/src/app/theme/header/header.component.ts new file mode 100644 index 00000000..04858b31 --- /dev/null +++ b/front/app/src/app/theme/header/header.component.ts @@ -0,0 +1,33 @@ +import { + Component, + Output, + EventEmitter, + Input, + ChangeDetectionStrategy, + ViewEncapsulation, + HostBinding, +} from '@angular/core'; +import screenfull from 'screenfull'; + +@Component({ + selector: 'app-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class HeaderComponent { + @HostBinding('class') class = 'matero-header'; + + @Input() showToggle = true; + @Input() showBranding = false; + + @Output() toggleSidenav = new EventEmitter(); + @Output() toggleSidenavNotice = new EventEmitter(); + + toggleFullscreen() { + if (screenfull.isEnabled) { + screenfull.toggle(); + } + } +} diff --git a/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html new file mode 100644 index 00000000..d4ee52cf --- /dev/null +++ b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.html @@ -0,0 +1,4 @@ + + Content 1 + Content 2 + diff --git a/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss new file mode 100644 index 00000000..c15cbe89 --- /dev/null +++ b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.scss @@ -0,0 +1,5 @@ +:host ::ng-deep { + .mat-mdc-tab { + min-width: 160px; + } +} diff --git a/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts new file mode 100644 index 00000000..d91c2c53 --- /dev/null +++ b/front/app/src/app/theme/sidebar-notice/sidebar-notice.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-sidebar-notice', + templateUrl: './sidebar-notice.component.html', + styleUrls: ['./sidebar-notice.component.scss'], +}) +export class SidebarNoticeComponent {} diff --git a/front/app/src/app/theme/sidebar/_sidebar-theme.scss b/front/app/src/app/theme/sidebar/_sidebar-theme.scss new file mode 100644 index 00000000..2507134e --- /dev/null +++ b/front/app/src/app/theme/sidebar/_sidebar-theme.scss @@ -0,0 +1,17 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-sidebar-header { + color: mat.get-color-from-palette($foreground, text); + background: mat.get-color-from-palette($background, dialog); + border-bottom: 1px solid mat.get-color-from-palette($foreground, divider); + } + + .matero-user-panel { + border-bottom: 1px solid mat.get-color-from-palette($foreground, divider); + } +} diff --git a/front/app/src/app/theme/sidebar/sidebar.component.html b/front/app/src/app/theme/sidebar/sidebar.component.html new file mode 100644 index 00000000..055275d3 --- /dev/null +++ b/front/app/src/app/theme/sidebar/sidebar.component.html @@ -0,0 +1,13 @@ +
+ + + + +
+ +
+ + +
diff --git a/front/app/src/app/theme/sidebar/sidebar.component.scss b/front/app/src/app/theme/sidebar/sidebar.component.scss new file mode 100644 index 00000000..83de6756 --- /dev/null +++ b/front/app/src/app/theme/sidebar/sidebar.component.scss @@ -0,0 +1,37 @@ +@use '../style/variables'; +@use '../style/breakpoints'; + +.matero-sidebar-header { + position: relative; + top: 0; + left: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: space-between; + height: variables.$toolbar-height-desktop; + padding: 0 8px; + overflow: hidden; + + @include breakpoints.bp-lt(small) { + & { + height: variables.$toolbar-height-mobile; + } + } + + // Colorful + .matero-header-white & { + background-color: white; + } +} + +.matero-sidebar-main { + height: calc(100% - #{variables.$toolbar-height-desktop}); + overflow: auto; + + @include breakpoints.bp-lt(small) { + & { + height: calc(100% - #{variables.$toolbar-height-mobile}); + } + } +} diff --git a/front/app/src/app/theme/sidebar/sidebar.component.ts b/front/app/src/app/theme/sidebar/sidebar.component.ts new file mode 100644 index 00000000..e681f657 --- /dev/null +++ b/front/app/src/app/theme/sidebar/sidebar.component.ts @@ -0,0 +1,16 @@ +import { Component, Output, EventEmitter, Input, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'app-sidebar', + templateUrl: './sidebar.component.html', + styleUrls: ['./sidebar.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class SidebarComponent { + @Input() showToggle = true; + @Input() showUser = true; + @Input() showHeader = true; + @Input() toggleChecked = false; + + @Output() toggleCollapsed = new EventEmitter(); +} diff --git a/front/app/src/app/theme/sidebar/user-panel.component.scss b/front/app/src/app/theme/sidebar/user-panel.component.scss new file mode 100644 index 00000000..5c439358 --- /dev/null +++ b/front/app/src/app/theme/sidebar/user-panel.component.scss @@ -0,0 +1,48 @@ +@use '../style/transitions'; + +.matero-user-panel { + display: flex; + flex-direction: column; + align-items: center; + padding: 12px 0; +} + +// Set default width and height can avoid flashing before avatar image loaded. +.matero-user-panel-avatar { + width: 64px; + height: 64px; + margin-bottom: 8px; + border-radius: 50rem; + transition: transitions.swift-ease-out(transform); +} + +.matero-user-panel-name, +.matero-user-panel-email { + margin-top: 0; + margin-bottom: 4px; + font-weight: normal; +} + +.matero-user-panel-name, +.matero-user-panel-email, +.matero-user-panel-icons { + opacity: 1; + transition: transitions.swift-ease-out(opacity); +} + +.matero-user-panel-icons { + white-space: nowrap; + + .mat-mdc-button-base, + .mat-mdc-button-touch-target { + width: 32px; + height: 32px; + } + + .mat-mdc-button-base { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + } +} diff --git a/front/app/src/app/theme/sidebar/user-panel.component.ts b/front/app/src/app/theme/sidebar/user-panel.component.ts new file mode 100644 index 00000000..ab859b53 --- /dev/null +++ b/front/app/src/app/theme/sidebar/user-panel.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService, User } from '@core/authentication'; + +@Component({ + selector: 'app-user-panel', + template: ` +
+ avatar +

{{ user.name }}

+ +
+ + + +
+
+ `, + styleUrls: ['./user-panel.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class UserPanelComponent implements OnInit { + user!: User; + + constructor(private router: Router, private auth: AuthService) {} + + ngOnInit(): void { + this.auth.user().subscribe(user => (this.user = user)); + } + + logout() { + this.auth.logout().subscribe(() => this.router.navigateByUrl('/auth/login')); + } +} diff --git a/front/app/src/app/theme/sidemenu/_sidemenu-theme.scss b/front/app/src/app/theme/sidemenu/_sidemenu-theme.scss new file mode 100644 index 00000000..c184cbba --- /dev/null +++ b/front/app/src/app/theme/sidemenu/_sidemenu-theme.scss @@ -0,0 +1,64 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $is-dark-theme: map.get($theme, is-dark); + $primary: map.get($theme, primary); + $accent: map.get($theme, accent); + $background: map.get($theme, background); + $foreground: map.get($theme, foreground); + + .matero-sidemenu { + > .menu-item { + > .menu-heading { + color: mat.get-color-from-palette($foreground, text); + + &:hover, + &:focus { + background: mat.get-color-from-palette($background, hover); + } + } + + &.active { + > .menu-heading { + color: + if( + $is-dark-theme, + color.adjust(mat.get-color-from-palette($primary), $lightness: 25%), + mat.get-color-from-palette($accent) + ); + background-color: if($is-dark-theme, rgba(mat.get-color-from-palette($primary), .15), transparent); + + &:hover, + &:focus { + background: mat.get-color-from-palette($background, hover); + } + } + } + + &.expanded { + background: mat.get-color-from-palette($background, hover); + } + } + + &.submenu { + > .menu-item { + &.active { + > .menu-heading { + background-color: transparent; + + &:hover, + &:focus { + background: mat.get-color-from-palette($background, hover); + } + } + } + + &.expanded { + background: transparent; + } + } + } + } +} diff --git a/front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts b/front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts new file mode 100644 index 00000000..c0bf016e --- /dev/null +++ b/front/app/src/app/theme/sidemenu/nav-accordion-item.directive.ts @@ -0,0 +1,43 @@ +import { Directive, HostBinding, Inject, Input, OnInit, OnDestroy } from '@angular/core'; +import { NavAccordionDirective } from './nav-accordion.directive'; + +@Directive({ + selector: '[navAccordionItem]', +}) +export class NavAccordionItemDirective implements OnInit, OnDestroy { + protected nav: NavAccordionDirective; + protected isExpanded = false; + + @Input() route = ''; + @Input() type: 'link' | 'sub' | 'extLink' | 'extTabLink' = 'link'; + + @HostBinding('class.expanded') + @Input() + get expanded() { + return this.isExpanded; + } + set expanded(value: boolean) { + // Only sub menu can be expanded + this.isExpanded = this.type === 'sub' && value; + + if (value) { + this.nav.closeOtherLinks(this); + } + } + + constructor(@Inject(NavAccordionDirective) nav: NavAccordionDirective) { + this.nav = nav; + } + + ngOnInit() { + this.nav.addLink(this); + } + + ngOnDestroy() { + this.nav.removeLink(this); + } + + toggle() { + this.expanded = !this.expanded; + } +} diff --git a/front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts b/front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts new file mode 100644 index 00000000..8c2e9156 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/nav-accordion-toggle.directive.ts @@ -0,0 +1,18 @@ +import { Directive, HostListener, Inject } from '@angular/core'; +import { NavAccordionItemDirective } from './nav-accordion-item.directive'; + +@Directive({ + selector: '[navAccordionToggle]', +}) +export class NavAccordionToggleDirective { + protected navLink: NavAccordionItemDirective; + + constructor(@Inject(NavAccordionItemDirective) navLink: NavAccordionItemDirective) { + this.navLink = navLink; + } + + @HostListener('click', ['$event']) + onClick() { + this.navLink.toggle(); + } +} diff --git a/front/app/src/app/theme/sidemenu/nav-accordion.directive.ts b/front/app/src/app/theme/sidemenu/nav-accordion.directive.ts new file mode 100644 index 00000000..a6a2538b --- /dev/null +++ b/front/app/src/app/theme/sidemenu/nav-accordion.directive.ts @@ -0,0 +1,53 @@ +import { Directive } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { MenuService } from '@core'; +import { filter } from 'rxjs/operators'; +import { NavAccordionItemDirective } from './nav-accordion-item.directive'; + +@Directive({ + selector: '[navAccordion]', +}) +export class NavAccordionDirective { + protected navLinks: NavAccordionItemDirective[] = []; + + constructor(private router: Router, private menu: MenuService) { + this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(() => this.checkOpenLinks()); + + // Fix opening status for async menu data + this.menu.change().subscribe(() => { + setTimeout(() => this.checkOpenLinks()); + }); + } + + addLink(link: NavAccordionItemDirective) { + this.navLinks.push(link); + } + + removeLink(link: NavAccordionItemDirective) { + const index = this.navLinks.indexOf(link); + if (index !== -1) { + this.navLinks.splice(index, 1); + } + } + + closeOtherLinks(openLink: NavAccordionItemDirective) { + this.navLinks.forEach(link => { + if (link !== openLink) { + link.expanded = false; + } + }); + } + + checkOpenLinks() { + this.navLinks.forEach(link => { + if (link.route) { + if (this.router.url.split('/').includes(link.route)) { + link.expanded = true; + this.closeOtherLinks(link); + } + } + }); + } +} diff --git a/front/app/src/app/theme/sidemenu/sidemenu.component.html b/front/app/src/app/theme/sidemenu/sidemenu.component.html new file mode 100644 index 00000000..fdf52aa7 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/sidemenu.component.html @@ -0,0 +1,70 @@ + + + + + + + + + {{item.icon}} + {{item.name | translate}} + + {{item.label.value}} + + + + {{item.badge.value}} + + + {{item.type==='sub' ? 'arrow_drop_down' : 'launch'}} + + diff --git a/front/app/src/app/theme/sidemenu/sidemenu.component.scss b/front/app/src/app/theme/sidemenu/sidemenu.component.scss new file mode 100644 index 00000000..1f1bcc35 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/sidemenu.component.scss @@ -0,0 +1,125 @@ +@use '../style/variables'; +@use '../style/transitions'; +@use '../style/badge'; + +.matero-sidemenu { + width: variables.$sidenav-width; + margin: 0; + padding: 0; + list-style: none; + + .menu-item { + display: block; + height: auto; + padding: 0; + + &.expanded { + > .submenu { + max-height: 2000px; + visibility: visible; + } + + > .menu-toggle > .menu-caret { + transform: rotate(-180deg); + } + } + } + + &.submenu { + max-height: 0; + padding-top: 0; + overflow: hidden; + transform: translateZ(0) !important; + visibility: hidden; + transition: transitions.fast-out-slow(max-height), transitions.fast-out-slow(visibility); + } + + .menu-heading { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + height: 48px; + padding: 0 16px; + font-size: inherit; + text-decoration: none; + background-color: transparent; + border: none; + outline: none; + cursor: pointer; + } + + .mat-icon.menu-icon { + width: 18px; + height: 18px; + margin-right: 16px; + font-size: 18px; + line-height: 18px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 16px; + } + } + + .mat-icon.menu-caret { + display: block; + text-align: center; + transition: transitions.fast-out-slow(transform); + } + + .menu-name, + .menu-label, + .menu-badge { + transition: transitions.swift-ease-out(opacity); + } + + .menu-label, + .menu-badge { + @include badge.badge(); + } + + .menu-badge { + border-radius: 50rem; + } + + .menu-spacer { + flex-grow: 1; + } + + &.level-0 > li > .menu-heading > .menu-name { + margin-right: 5px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 5px; + } + } + + &.level-1 > li > .menu-heading { + padding-left: 50px; + + [dir='rtl'] & { + padding-right: 50px; + padding-left: 16px; + } + } + + &.level-2 > li > .menu-heading { + padding-left: 64px; + + [dir='rtl'] & { + padding-right: 64px; + padding-left: 16px; + } + } + + &.level-2 [class^='level-'] > li > .menu-heading { + padding-left: 80px; + + [dir='rtl'] & { + padding-right: 80px; + padding-left: 16px; + } + } +} diff --git a/front/app/src/app/theme/sidemenu/sidemenu.component.ts b/front/app/src/app/theme/sidemenu/sidemenu.component.ts new file mode 100644 index 00000000..96fe29b6 --- /dev/null +++ b/front/app/src/app/theme/sidemenu/sidemenu.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, ViewEncapsulation } from '@angular/core'; +import { MenuService } from '@core'; + +@Component({ + selector: 'app-sidemenu', + templateUrl: './sidemenu.component.html', + styleUrls: ['./sidemenu.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class SidemenuComponent { + // Note: Ripple effect make page flashing on mobile + @Input() ripple = false; + + menu$ = this.menu.getAll(); + + buildRoute = this.menu.buildRoute; + + constructor(private menu: MenuService) {} +} diff --git a/front/app/src/app/theme/style.scss b/front/app/src/app/theme/style.scss new file mode 100644 index 00000000..63b0a6bc --- /dev/null +++ b/front/app/src/app/theme/style.scss @@ -0,0 +1,3 @@ +@use 'style/reboot'; +@use 'style/misc'; +@use 'style/colors'; diff --git a/front/app/src/app/theme/style/_badge.scss b/front/app/src/app/theme/style/_badge.scss new file mode 100644 index 00000000..18f2c600 --- /dev/null +++ b/front/app/src/app/theme/style/_badge.scss @@ -0,0 +1,14 @@ +@mixin badge() { + display: inline-block; + min-width: 18px; + padding: .35em .65em; + color: #fff; + font-weight: 700; + font-size: .75em; + line-height: 1; + white-space: nowrap; + text-align: center; + vertical-align: baseline; + background-color: #757575; + border-radius: 4px; +} diff --git a/front/app/src/app/theme/style/_breakpoints.scss b/front/app/src/app/theme/style/_breakpoints.scss new file mode 100644 index 00000000..f4225d2d --- /dev/null +++ b/front/app/src/app/theme/style/_breakpoints.scss @@ -0,0 +1,36 @@ +@use 'sass:map'; +@use 'variables'; + +@function bp($name, $breakpoints: variables.$breakpoints) { + $min: map.get($breakpoints, $name); + + @return $min; +} + +// Media of at least the minimum breakpoint width. +@mixin bp-gt($name, $breakpoints: variables.$breakpoints) { + $min: bp($name, $breakpoints); + + @if $min { + @media (min-width: $min) { + @content; + } + } + @else { + @content; + } +} + +// Media of at most the maximum breakpoint width. +@mixin bp-lt($name, $breakpoints: variables.$breakpoints) { + $max: bp($name, $breakpoints) - 1px; + + @if $max { + @media (max-width: $max) { + @content; + } + } + @else { + @content; + } +} diff --git a/front/app/src/app/theme/style/_colors.scss b/front/app/src/app/theme/style/_colors.scss new file mode 100644 index 00000000..d69577fa --- /dev/null +++ b/front/app/src/app/theme/style/_colors.scss @@ -0,0 +1,40 @@ +// Material color helpers including text color and background color +@use 'variables'; + +@mixin generate-colors($prefix, $property) { + @each $name, $value in variables.$mat-colors { + // If the value is a map, continue to each + @if type-of($value) == 'map' { + @each $hue, $color in $value { + @if ($hue != 'contrast') { + .#{$prefix + '-' + $name + '-' + $hue} { + #{$property}: $color !important; + } + } + } + } + + @if type-of($value) == 'color' { + .#{$prefix + '-' + $name} { + #{$property}: $value !important; + } + } + } + + // alias + @for $i from 1 through 9 { + .#{$prefix + '-grey-' + $i * 100} { + @extend .#{$prefix + '-gray-' + $i * 100}; + } + + .#{$prefix + '-blue-grey-' + $i * 100} { + @extend .#{$prefix + '-blue-gray-' + $i * 100}; + } + } +} + +// Generate text color helpers +@include generate-colors('text', 'color'); + +// Generate background color helpers +@include generate-colors('bg', 'background-color'); diff --git a/front/app/src/app/theme/style/_misc.scss b/front/app/src/app/theme/style/_misc.scss new file mode 100644 index 00000000..ebba0360 --- /dev/null +++ b/front/app/src/app/theme/style/_misc.scss @@ -0,0 +1,25 @@ +@use 'variables'; +@use 'badge'; +@use 'scrollbar'; + +.badge { + @include badge.badge(); +} + +.scrollbar-thin { + @include scrollbar.thin(); +} + +.scrollbar-none { + @include scrollbar.none(); +} + +@media (max-width: 720px) { + .hide-small { + display: none !important; + } + + .show-small { + display: block !important; + } +} diff --git a/front/app/src/app/theme/style/_reboot-theme.scss b/front/app/src/app/theme/style/_reboot-theme.scss new file mode 100644 index 00000000..982bd910 --- /dev/null +++ b/front/app/src/app/theme/style/_reboot-theme.scss @@ -0,0 +1,27 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $primary: map.get($theme, primary); + $foreground: map.get($theme, foreground); + $is-dark-theme: map.get($theme, is-dark); + + @if $is-dark-theme { + :root { + color-scheme: dark; + } + } + + a:not(.mat-mdc-button-base) { + color: mat.get-color-from-palette($primary); + + &:hover { + color: color.adjust(mat.get-color-from-palette($primary), $lightness: 10%); + } + } + + code { + background-color: rgba(mat.get-color-from-palette($foreground, secondary-text), .03); + } +} diff --git a/front/app/src/app/theme/style/_reboot.scss b/front/app/src/app/theme/style/_reboot.scss new file mode 100644 index 00000000..5a990356 --- /dev/null +++ b/front/app/src/app/theme/style/_reboot.scss @@ -0,0 +1,48 @@ +@use 'variables'; + +body { + margin: 0; + font-family: variables.$font-family-base; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; +} + +html, +body { + position: relative; // 1 + height: 100%; + overflow: auto; // 2 +} + +*, +::after, +::before { + box-sizing: border-box; +} + +dl, +ol, +ul { + margin-top: 0; + margin-bottom: 1rem; +} + +code, +kbd, +pre, +samp { + font-family: variables.$font-family-monospace; +} + +code { + padding: 3px; + font-size: 90%; + word-break: break-word; +} + +a { + text-decoration: none; +} diff --git a/front/app/src/app/theme/style/_rtl.scss b/front/app/src/app/theme/style/_rtl.scss new file mode 100644 index 00000000..406f7297 --- /dev/null +++ b/front/app/src/app/theme/style/_rtl.scss @@ -0,0 +1,5 @@ +@mixin rtl { + @at-root [dir='rtl'] #{&} { + @content; + } +} diff --git a/front/app/src/app/theme/style/_scrollbar.scss b/front/app/src/app/theme/style/_scrollbar.scss new file mode 100644 index 00000000..a9cc68c7 --- /dev/null +++ b/front/app/src/app/theme/style/_scrollbar.scss @@ -0,0 +1,44 @@ +@mixin thin() { + &::-webkit-scrollbar { + width: 4px; + height: 4px; + } + + &::-webkit-scrollbar-track, + &::-webkit-scrollbar-thumb { + border-color: transparent; + border-style: solid; + border-width: 0; + border-radius: 50rem; + border-image: initial; + } + + &::-webkit-scrollbar-track { + box-shadow: rgba(0, 0, 0, .2) 1px 1px 5px inset; + } + + &::-webkit-scrollbar-thumb { + min-height: 20px; + background-color: rgba(0, 0, 0, .2); + } + + &::-webkit-scrollbar-corner { + background: transparent; + } + + & { + scrollbar-width: thin; + } +} + +@mixin none { + &::-webkit-scrollbar { + width: 0; + height: 0; + } + + & { + -ms-overflow-style: none; + scrollbar-width: none; + } +} diff --git a/front/app/src/app/theme/style/_transitions.scss b/front/app/src/app/theme/style/_transitions.scss new file mode 100644 index 00000000..bec053b5 --- /dev/null +++ b/front/app/src/app/theme/style/_transitions.scss @@ -0,0 +1,15 @@ +@use 'variables'; + +@function swift-ease-out($property) { + // The Material default animation curves. + $transition: $property variables.$swift-ease-out-duration variables.$swift-ease-out-timing-function; + + @return $transition; +} + +@function fast-out-slow($property) { + // The Material default animation curves. + $transition: $property 225ms cubic-bezier(.4, 0, .2, 1); + + @return $transition; +} diff --git a/front/app/src/app/theme/style/_variables.scss b/front/app/src/app/theme/style/_variables.scss new file mode 100644 index 00000000..2c939697 --- /dev/null +++ b/front/app/src/app/theme/style/_variables.scss @@ -0,0 +1,66 @@ +@use '@angular/material' as mat; + +// Layout +$gutter: 16px !default; + +// Sidenav +$sidenav-width: 240px !default; +$sidenav-collapsed-width: 50px !default; +$sidenav-width-mobile: 280px !default; + +// Toolbar +$toolbar-height-desktop: 64px !default; +$toolbar-height-mobile: 56px !default; + +// Topmenu +$topmenu-sticky-position-desktop: $toolbar-height-desktop !default; +$topmenu-sticky-position-mobile: $toolbar-height-mobile !default; + +// Typography +$font-family-sans-serif: 'Roboto', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, + 'Lucida Grande', sans-serif !default; +$font-family-monospace: 'Roboto Mono', monospace !default; +$font-family-base: $font-family-sans-serif !default; + +// Breakpoints +// +// Define the minimum dimensions at which your layout will change, +// adapting to different screen sizes, for use in media queries. +$breakpoints: ( + xsmall: 0, + small: 600px, + medium: 960px, + large: 1280px, + xlarge: 1920px +) !default; + +// Material colors +$mat-colors: ( + red: mat.$red-palette, + pink: mat.$pink-palette, + purple: mat.$purple-palette, + deep-purple: mat.$deep-purple-palette, + indigo: mat.$indigo-palette, + blue: mat.$blue-palette, + light-blue: mat.$light-blue-palette, + cyan: mat.$cyan-palette, + teal: mat.$teal-palette, + green: mat.$green-palette, + light-green: mat.$light-green-palette, + lime: mat.$lime-palette, + yellow: mat.$yellow-palette, + amber: mat.$amber-palette, + orange: mat.$orange-palette, + deep-orange: mat.$deep-orange-palette, + brown: mat.$brown-palette, + gray: mat.$gray-palette, + blue-gray: mat.$blue-gray-palette, + white: white, + black: black, + light: white, + dark: rgba(black, .87), +) !default; + +// The material default animation curves +$swift-ease-out-duration: 400ms !default; +$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default; diff --git a/front/app/src/app/theme/theme.module.ts b/front/app/src/app/theme/theme.module.ts new file mode 100644 index 00000000..76c5d2bc --- /dev/null +++ b/front/app/src/app/theme/theme.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '@shared/shared.module'; + +import { AdminLayoutComponent } from './admin-layout/admin-layout.component'; +import { AuthLayoutComponent } from './auth-layout/auth-layout.component'; + +import { SidebarComponent } from './sidebar/sidebar.component'; +import { UserPanelComponent } from './sidebar/user-panel.component'; +import { SidemenuComponent } from './sidemenu/sidemenu.component'; +import { NavAccordionDirective } from './sidemenu/nav-accordion.directive'; +import { NavAccordionItemDirective } from './sidemenu/nav-accordion-item.directive'; +import { NavAccordionToggleDirective } from './sidemenu/nav-accordion-toggle.directive'; +import { SidebarNoticeComponent } from './sidebar-notice/sidebar-notice.component'; + +import { TopmenuComponent } from './topmenu/topmenu.component'; +import { TopmenuPanelComponent } from './topmenu/topmenu-panel.component'; + +import { HeaderComponent } from './header/header.component'; + +import { BrandingComponent } from './widgets/branding.component'; +import { NotificationComponent } from './widgets/notification.component'; +import { TranslateComponent } from './widgets/translate.component'; +import { UserComponent } from './widgets/user.component'; + +@NgModule({ + declarations: [ + AdminLayoutComponent, + AuthLayoutComponent, + SidebarComponent, + UserPanelComponent, + SidemenuComponent, + NavAccordionDirective, + NavAccordionItemDirective, + NavAccordionToggleDirective, + SidebarNoticeComponent, + TopmenuComponent, + TopmenuPanelComponent, + HeaderComponent, + BrandingComponent, + NotificationComponent, + TranslateComponent, + UserComponent, + ], + imports: [SharedModule], +}) +export class ThemeModule {} diff --git a/front/app/src/app/theme/topmenu/_topmenu-theme.scss b/front/app/src/app/theme/topmenu/_topmenu-theme.scss new file mode 100644 index 00000000..f93cf2a1 --- /dev/null +++ b/front/app/src/app/theme/topmenu/_topmenu-theme.scss @@ -0,0 +1,32 @@ +@use 'sass:map'; +@use '@angular/material' as mat; + +@mixin theme($theme) { + $is-dark-theme: map.get($theme, is-dark); + $primary: map.get($theme, primary); + $accent: map.get($theme, accent); + $background: mat.get-color-from-palette($theme, background); + $foreground: mat.get-color-from-palette($theme, foreground); + + .matero-topmenu { + background: mat.get-color-from-palette($background, app-bar); + + .mat-mdc-button { + color: mat.get-color-from-palette($foreground, text); + + &.active { + background-color: mat.get-color-from-palette($background, focused-button); + } + } + } + + .matero-topmenu-panel { + .mat-mdc-menu-item { + color: mat.get-color-from-palette($foreground, text); + + &.active > .mdc-list-item__primary-text { + color: mat.get-color-from-palette($accent); + } + } + } +} diff --git a/front/app/src/app/theme/topmenu/topmenu-panel.component.html b/front/app/src/app/theme/topmenu/topmenu-panel.component.html new file mode 100644 index 00000000..af7de4b2 --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu-panel.component.html @@ -0,0 +1,38 @@ + + + + + + {{item.name | translate}} + + + + {{item.name | translate}} + launch + + + + {{item.name | translate}} + launch + + + + + + + diff --git a/front/app/src/app/theme/topmenu/topmenu-panel.component.ts b/front/app/src/app/theme/topmenu/topmenu-panel.component.ts new file mode 100644 index 00000000..023b6ea9 --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu-panel.component.ts @@ -0,0 +1,82 @@ +import { + Component, + ViewChild, + Input, + Output, + EventEmitter, + OnInit, + OnDestroy, +} from '@angular/core'; +import { MatMenu } from '@angular/material/menu'; +import { NavigationEnd, Router, RouterLinkActive } from '@angular/router'; +import { MenuChildrenItem, MenuService } from '@core'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { TopmenuState } from './topmenu.component'; + +@Component({ + selector: 'app-topmenu-panel', + templateUrl: './topmenu-panel.component.html', +}) +export class TopmenuPanelComponent implements OnInit, OnDestroy { + @ViewChild(MatMenu, { static: true }) menuPanel!: MatMenu; + + @Input() items: MenuChildrenItem[] = []; + @Input() parentRoute: string[] = []; + @Input() level = 1; + @Output() routeChange = new EventEmitter(); + + menuStates: TopmenuState[] = []; + + buildRoute = this.menu.buildRoute; + + private routerSubscription = Subscription.EMPTY; + + constructor(private menu: MenuService, private router: Router) {} + + ngOnInit() { + this.items.forEach(item => { + this.menuStates.push({ active: this.checkRoute(item), route: item.route }); + }); + } + + ngOnDestroy() { + this.routerSubscription.unsubscribe(); + } + + checkRoute(item: MenuChildrenItem) { + if (!item.route) { + return this.checkChildRoute(item.children); + } else { + return this.router.url.split('/').includes(item.route); + } + } + + checkChildRoute(menuItems: MenuChildrenItem[] = []) { + return menuItems.some(child => { + if (this.router.url.split('/').includes(child.route)) { + return true; + } + if (!child.route && child.children) { + this.checkChildRoute(child.children); + } + return false; + }); + } + + onRouterLinkClick(rla: RouterLinkActive) { + this.routeChange.emit(rla); + } + + onRouteChange(rla: RouterLinkActive, index: number) { + this.routeChange.emit(rla); + + this.routerSubscription.unsubscribe(); + this.routerSubscription = this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(e => { + this.menuStates.forEach(item => (item.active = false)); + setTimeout(() => (this.menuStates[index].active = rla.isActive)); + }); + } +} diff --git a/front/app/src/app/theme/topmenu/topmenu.component.html b/front/app/src/app/theme/topmenu/topmenu.component.html new file mode 100644 index 00000000..50bc01af --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu.component.html @@ -0,0 +1,53 @@ + + + + + {{item.icon}} + {{item.name | translate}} + + {{item.label.value}} + + + {{item.badge.value}} + + + {{item.type==='sub' ? 'arrow_drop_down' : 'launch'}} + + diff --git a/front/app/src/app/theme/topmenu/topmenu.component.scss b/front/app/src/app/theme/topmenu/topmenu.component.scss new file mode 100644 index 00000000..a227cb1a --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu.component.scss @@ -0,0 +1,77 @@ +@use '@angular/material' as mat; + +.matero-topmenu { + position: sticky; + z-index: 200; + display: block; + padding: 8px; + + @include mat.elevation(2); + + .mat-mdc-button-base { + padding: 0 16px; + white-space: nowrap; + } + + .menu-icon, + .menu-caret, + .menu-name { + vertical-align: middle; + } + + .mat-icon.menu-icon { + width: 18px; + height: 18px; + margin-right: 8px; + font-size: 18px; + line-height: 18px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 8px; + } + } + + .mat-icon.menu-caret { + margin-right: -8px; + + [dir='rtl'] & { + margin-right: auto; + margin-left: -8px; + } + } + + .menu-label, + .menu-badge { + margin-left: 8px; + font-size: 12px; + + [dir='rtl'] & { + margin-right: 8px; + margin-left: auto; + } + } + + .menu-badge { + border-radius: 50rem; + } + + .mat-tab-nav-bar, + .mat-tab-header { + border-bottom: none; + } +} + +.matero-topmenu-panel { + .mat-menu-item { + .menu-name { + margin-right: 8px; + vertical-align: middle; + + [dir='rtl'] & { + margin-right: auto; + margin-left: 8px; + } + } + } +} diff --git a/front/app/src/app/theme/topmenu/topmenu.component.ts b/front/app/src/app/theme/topmenu/topmenu.component.ts new file mode 100644 index 00000000..ce08e6c4 --- /dev/null +++ b/front/app/src/app/theme/topmenu/topmenu.component.ts @@ -0,0 +1,57 @@ +import { Component, HostBinding, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { NavigationEnd, Router, RouterLinkActive } from '@angular/router'; +import { Menu, MenuService } from '@core'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +export interface TopmenuState { + active: boolean; + route: string; +} + +@Component({ + selector: 'app-topmenu', + templateUrl: './topmenu.component.html', + styleUrls: ['./topmenu.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class TopmenuComponent implements OnDestroy { + @HostBinding('class') class = 'matero-topmenu'; + + menu$ = this.menu.getAll(); + + buildRoute = this.menu.buildRoute; + + menuList: Menu[] = []; + menuStates: TopmenuState[] = []; + + private menuSubscription = Subscription.EMPTY; + private routerSubscription = Subscription.EMPTY; + + constructor(private menu: MenuService, private router: Router) { + this.menuSubscription = this.menu$.subscribe(res => { + this.menuList = res; + this.menuList.forEach(item => { + this.menuStates.push({ + active: this.router.url.split('/').includes(item.route), + route: item.route, + }); + }); + }); + } + + ngOnDestroy() { + this.menuSubscription.unsubscribe(); + this.routerSubscription.unsubscribe(); + } + + onRouteChange(rla: RouterLinkActive, index: number) { + this.routerSubscription.unsubscribe(); + this.routerSubscription = this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe(e => { + this.menuStates.forEach(item => (item.active = false)); + setTimeout(() => (this.menuStates[index].active = rla.isActive)); + }); + } +} diff --git a/front/app/src/app/theme/widgets/branding.component.ts b/front/app/src/app/theme/widgets/branding.component.ts new file mode 100644 index 00000000..b1d1ad62 --- /dev/null +++ b/front/app/src/app/theme/widgets/branding.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-branding', + template: ` + + + MATERO + + `, + styles: [ + ` + .brand-logo { + width: 30px; + height: 30px; + } + `, + ], +}) +export class BrandingComponent {} diff --git a/front/app/src/app/theme/widgets/github.component.ts b/front/app/src/app/theme/widgets/github.component.ts new file mode 100644 index 00000000..f4fa3536 --- /dev/null +++ b/front/app/src/app/theme/widgets/github.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-github-button', + template: ` + + + + + + `, +}) +export class GithubButtonComponent {} diff --git a/front/app/src/app/theme/widgets/notification.component.ts b/front/app/src/app/theme/widgets/notification.component.ts new file mode 100644 index 00000000..45648f49 --- /dev/null +++ b/front/app/src/app/theme/widgets/notification.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-notification', + template: ` + + + + + + info + {{ message }} + + + + `, +}) +export class NotificationComponent { + messages = ['Server Error Reports', 'Server Error Reports', 'Server Error Reports']; +} diff --git a/front/app/src/app/theme/widgets/translate.component.ts b/front/app/src/app/theme/widgets/translate.component.ts new file mode 100644 index 00000000..da7ac3a6 --- /dev/null +++ b/front/app/src/app/theme/widgets/translate.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { SettingsService } from '@core'; + +@Component({ + selector: 'app-translate', + template: ` + + + + + + `, +}) +export class TranslateComponent { + langs = { + 'en-US': 'English', + 'zh-CN': '中文简体', + 'zh-TW': '中文繁体', + }; + + constructor(private translate: TranslateService, private settings: SettingsService) { + translate.addLangs(['en-US', 'zh-CN', 'zh-TW']); + } + + useLanguage(language: string) { + this.translate.use(language); + this.settings.setLanguage(language); + } +} diff --git a/front/app/src/app/theme/widgets/user.component.ts b/front/app/src/app/theme/widgets/user.component.ts new file mode 100644 index 00000000..60dca4d5 --- /dev/null +++ b/front/app/src/app/theme/widgets/user.component.ts @@ -0,0 +1,56 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { debounceTime, tap } from 'rxjs/operators'; +import { AuthService, User } from '@core/authentication'; + +@Component({ + selector: 'app-user', + template: ` + + + + + + + + `, + styles: [ + ` + .avatar { + width: 24px; + height: 24px; + } + `, + ], +}) +export class UserComponent implements OnInit { + user!: User; + + constructor(private router: Router, private auth: AuthService, private cdr: ChangeDetectorRef) {} + + ngOnInit(): void { + this.auth + .user() + .pipe( + tap(user => (this.user = user)), + debounceTime(10) + ) + .subscribe(() => this.cdr.detectChanges()); + } + + logout() { + this.auth.logout().subscribe(() => this.router.navigateByUrl('/auth/login')); + } +} diff --git a/front/app/src/assets/data/dashboard.json b/front/app/src/assets/data/dashboard.json new file mode 100644 index 00000000..93b5aca9 --- /dev/null +++ b/front/app/src/assets/data/dashboard.json @@ -0,0 +1,28 @@ +{ + "dashboard": [ + { + "type": "Total Sales", + "amount": 180200, + "progress": 50, + "date": 1427207139000 + }, + { + "type": "Revenue", + "amount": 70205, + "progress": 70, + "date": 1427412725000 + }, + { + "type": "Traffic", + "amount": 1291922, + "progress": 80, + "date": 1427546580000 + }, + { + "type": "New User", + "amount": 1922, + "progress": 40, + "date": 1427891640000 + } + ] +} diff --git a/front/app/src/assets/data/menu.json b/front/app/src/assets/data/menu.json new file mode 100644 index 00000000..7e1bcf3c --- /dev/null +++ b/front/app/src/assets/data/menu.json @@ -0,0 +1,37 @@ +{ + "menu": [ + { + "route": "dashboard", + "name": "dashboard", + "type": "link", + "icon": "dashboard", + "badge": { + "color": "red-500", + "value": "5" + } + }, + { + "route": "/", + "name": "sessions", + "type": "sub", + "icon": "question_answer", + "children": [ + { + "route": "403", + "name": "403", + "type": "link" + }, + { + "route": "404", + "name": "404", + "type": "link" + }, + { + "route": "500", + "name": "500", + "type": "link" + } + ] + } + ] +} diff --git a/front/app/src/assets/fonts/Material_Icons.css b/front/app/src/assets/fonts/Material_Icons.css new file mode 100644 index 00000000..95f3add8 --- /dev/null +++ b/front/app/src/assets/fonts/Material_Icons.css @@ -0,0 +1,23 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url(./flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/front/app/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 b/front/app/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..34cdd2afba56b9dd3c7f0e3b758069a92db6b7bf GIT binary patch literal 60840 zcmV(+K;6H0Pew8T0RR910PUy%4gdfE0=B#W0PRo!0RR9100000000000000000000 z0000Q92*cEf{-c(U;vRc5eN#1h-ik3YzwDk00A}vBm1dIum|XCrc=@OD3= zD0NpAa-00~<3+*Pwc&OI?aiH!uVCXUGw2SG2-kMsWdHwvYSNIyk}FBC3f66=N^t=L+q;3n}>;b$!DFrV=% zpd6c1BUw3l@+F68o5usf11g|k6gGllV@|QK603Ssx3*TUN*88r+Cx`vjkRc(;g2|b zO>zm_f@eu*QS>DH^Z~gY77z!(+;xDs!ky5W?kp+qXRb{C-bcd!PGe2HqFMB#Bq?5WS04qBYTKk!C5A z5|wc_6Om#0{OZeOQl$;KkP9_JjZmXbja&#XqU-d()6B2>no_5XL$)o$p3IrI+I(Ht z#9@puMu@{;#f91AW*$lm~YqgEB7Bf$ciO z)tR3Ex%^))&)vI`^ePk%V>U1Bgtlbf#8{3!$5Wa^eP>Hkbm+nTv<$-3nF=j|ATRlJ z#ev?2a;m5Hch~+5MUYO063FrpQs-cqM~T|`^R>Up_rMSsSOzR`s0@Z7goe(rS^5C; z=9hTzPOYDjAI?Y*+bohSwOt}xF5Gd!u?Ldqr;awOpI`ac{-wY!2MPyNSU&0=Q5+v| z59Z6I{>(qlTt}k?Lt$Blf`MCh3Ou7ptVP-=E1l`wz_l6iWpEv!I_B1vp00n8wo0MNJEeR-=x{7={Clqo7Lv3-ZBdJ zJ-kP}hYN@gBqaf&Ws#0lCM9`HC*lF5Ku~s`vrZFok|)X=QYgx;LP$lkRk!W(eEmJs zd+e_z3G4(Q^=)Tf@R|uM3rIt@{=QVLE1EPtq9f!<@I9lY&;063`I603>+{$aILZ zG$AEv#yD|UNt%{TpMq!7MK*qfOe~51XAKh;(#G=S& zmZ6~RuB2Ia$z2KwfY1R1fEvn>R)K&xOZLWH2qob0%Fvo{P=gxY*=3wyIS1n=nm^$|M!9YFUQ}F z(9zG{7sG19lLvMAzboSkf3NPWiLR*!=2*>E_G_iJ4tg14q{-%5I+{jmN*+xDkD;$% z@TZ|~c7V<))H#6~$57=6DjY(Y11PZ%MK1U>3-8y*)KfDCByMNzW`obDi*9wSwq;qZ z-r(KpEuU$i(6X7`99}J%;gkZ72oX^@?6K)Oqw3j%VV_u7hA7*D@pOh~3{)mZ?hla6 zlzIA0B@1ooZjr3CwabxgwVi7t*=q-K|G{1CVw3K($;@w7ds&CJ0iXBTj_&I=%l3X( zOE`v(3njb)LEOimu_2+uGBXa^%eM+)NtqhyDsT7Og^`6XU3X%1%m`A%N;W2i%<>a= zl8=+GIC$*TnFkl0IalcDyHr=h&DW{kdKZ0bS^ghOd-n0}7k~8N5oz^bEsB6fVF5}A zKtxIk3rEqQ;3No52}vj-;4?LKwT}5-iLg!XFa{E+kJzx`)e@v*I{C@S<3kw-4jLddC&$fH&ZKejzxB1^(k`SvE0n;67%Ru}n1i=ZBWEQ#3z9dsrS!?${qrQ(+EjUL_Y}4|00NUBm-yco$5w zSF-Hx&q=`VyVezruCpkMbV+Qt&}0Rx-nw&Sh1ckH{Zp~Lne}Oy+3fFjERAW_NM50A zUpOO9g@U>Au|_YcL^$5?Q9g!*-SQuTI6Yk4x@GG z5G--ej4E--2Vr<_=sAqR-?v^U!P$cQwR)LQ64&4cPe4a($iUQNS^5Pga!XIkL=TdK z82*+_O{7M<5Nzw$4PvFZ7DN;{4$lcR*_FYWvFgg&3oJkLuZFbEjjM-SZmn~LH`yL% zV)1pcx|+7}^}w=L*BTXX7yEx;Y)wcf^|>)oPvS|#Lf>`|bG?~>exuh-UqlDsN#?_} znq3(fhv^vQ!EX(?=mWXo*4%sdl!W;=ww`~Vbx}BrBvB96Fqn3zbeyI^JBJ=8Sl&n8 zuM{hF?A@kqBB{Qb95B&d51MrEi%SwYPj4Qi#WG{eIVL;T)0^|~*6_XAq7{3&^-UHx zVn9X%j0$7`2pwnfpvs0e^EC81;+9B}$z8G%c@$PwZSitd*GQ2*lsdCNa){X9>e&EZ z^wYc|Nh$y9(2}~aIGd|N ziCpJH0p45H`Xw%Rf{`-mbo-Nr*ypT6a9PGaj>gGuG4kV?GpcQa-0{-lX#l_53e_yx~Q=o zf35}ET)<}YkbqFfx;DMFVC8PpAZ1$`ONn6-iNmhLtdAJiL)>a|(!76Qwuq$k7EW3h zOQ`{Q;zcv6CZj&f zpC|CgGm-xS&%L>Ha2M#5o>!-y#Lj9y!s$3i{P7Srz_ergWs3)eq4M2&GPHj{{yl)U z7_*A6*R_fGQzHqQ!4b@dHZwz7lZszaL-v;Y4VxeZx)XVi$*d8Hfl72s$tB_lNr~?P z&s?84z50U&r>|YEaW6oy6CRcBwCj7;KmKZtsZ3$E6ZTkId)v!4S;89cNPtj*juGw< z?o>IDuuYdU8j8BUDug6-sk#!OMt8rRIA~_au17w4$bv`Y6oFw;G1EzwJpP!E!zJfC za-}n89~|D#(T%NoFG@WqENQ;&^0iy-pxC#j?aKZCOA$C?ZYu8?^uuVfkXPe9$w($t zHm2nY7WMHgN*kBD29@lPcC$Vo+1TE;xI$6kAsr9z$hiD-&x?bW;g#S9PylY+jV2+vb$PVE_*ju?J@cRY-LCD`Z~^Qpsg? zD~PgL23gpKXfQtW};T5AO|! zFPc`XfsU$AGF*J{jA|kX*f?QL2>3)b19&-*w_ zBaG2Avgg^RkQMZKaLDB5c3C!Sd0?Jf0WY$?Gtt`fWlh;p?77XZqflMF6-2E%dtLer z6oadqMU8g@;>DXOaklB_Bc8!TsIW(R1>d=*VUJ5OLfNHz_mBjbg)z6DPawG|rZ0Us z3jASwq&J&2ZZIV-JciK&7WmK{sC)|6e{c_hv%9r^aNk6!NSmmCl@L0wTH*kw|$HRFdW?>blK1KD* zrA<;AE%hwb=hJ(2;;fvztQMPLvh3zG)Tp$6jQe(2)^37(&WgKt-lSv_*Z&wf5EkV` z*cA^ZMvH<=%$MJI6yfpI|2MleusbYSbm;LXPx{hTY!d|ezW?R=In5;IbYRXCc3*wB z<$JlEJ+DUt(1Adq00eTvsB>VHk~%viHjLW!RwSROq(qf9(}x-bOWMs_HhIrJBNHP8c(h0}qfy&`xrg9Zf6F{*1+n*sPf5tX{Ab8(F9nHXa0N;N^y`iE( zedE+P#F2yqB->o>Co&T38vOitzKD_4ad2e$*EvNOyr9%y5QtQK5`AyuiZO-6X1)29 z3S0Gi8u-N5-Mrq`M5#@7Hr@m%yKdFIiM0Kq-^rLyrZ2TPA>#wJm|4dO6kvb-t*W+N z`9vw@y|Av4qG}E~UkDKH7zwVT56A395@_v>32p1dc=~qNY+;%Z|H8qu?^{v3fW^w^ zeX@DOU)IZgd#=ALjKl3+8szbH-FbPJF(s7}XYX@&Yu-~L@Zrs~3=Az9B4&TWGTB91 zpzsH>Sw-+fhl`qHjK4)m1#k1aP5NTWhsj|-5=p6YRH1xkszU2^Y*4UX=Oj%-lbTMT zPSA*nx*828UajOl&LS-yC#ud)-l$@OUQpm+FX7mjgfSxf9QuUC(v*kZP~HU|)~$L9y=NEf2Xa_#LNvQaLeF*!VM;xJ%jhT`#btK`RP+)eS; zQjV0;>7JZ&3CR)i=@if=st0)2h(0~Yg&2io+dCEtMy1xWc^mTN;0Sj-tTnn=Ccp-4C^wx`xRDH+@Lu~ZN+w64U~B_??cbv z9LYk;jzgcnws7Q1x;XN;#D|_{X|aeSU>2@5s6aEt|IAZ83t4%`cLE&%a3V3~p{QEb zQ{L?=`44of!IhhNF;)q;*fw*(wvgL>_j5m(6i6z+-{ai_HEBapt~UcK!Qz_*rvt3F z&E)PKfQyRtOg3HBJIH=WzSpI#RQpnGNfm_-kh+q>xr-C7+>RN z35kpvNEVEqpLp&~7rxS+o_~X*D=$>_Ry3-sxY??zt#;P8V#CudhL_$MyYbH5UAwiD zo$r;n0vWjnENWQJb7{EHBY#nPE_Ytlzc?&eg=*#$3dMFl(xh$c*&y06ssMn3QcPRp zJGPFb|H5MtuB;G4E#yeYMRSf)oQhIks$9CH8moQO+pF%PnI1e6BIt5Z0|^W_^X#Ni z&?%r&F;h~Ln2Kbf@_l$C(xCXCLUAPkFtUBxCp$NAze4_&>tdAtM48)`1QC;!yQ|F{ zlm^*lcwv$)Hk6P3)^8dDBHDrnwSW`-9Ine(E# zU2E#S5!kZevwT&S!&EP(XZ6CEt?s+n7>!@F)B(7ZUqDFnX}&hXP}SCP$IF8d;HV5G zQ}o6gldI`Nx>~^kSLetc`J8mlgE<7% zZ|^r_{wy7U@>chm&7!>Uhh+TN>Fz{l(ecxeZ;-_S_1jmIMRs5%*)`>825qv~NYO22 zg3ao-m?i4c*F|#*b&>5XcOP{O^taWOAd*?GxM}wd)vb1)(xOlUh;aL@9M}Y@0v?3k zCT7LcHwZPCeg=(Q=D?Oq(V%UUm*5nbcL~JI|L&oMe1%D4*|OvPf)dvVD0MLx-`?tf z{o3CYF?gBSiNi)Oa}`(vG2bh{d+uPuZt2JS4Kf4ekYC?38amcGE(N8`j@>;ygYJ`8 ze=_bl`-ff0$b4*=?klSLFT2N1Ekb zdph3Xbl$h+pv-Qky=ZT5%$wxR`{vl^=)oY_AD4S8CiM5RLDUfBPb6;<9_W90-FKHaqT`SH3qyP@@)hxbXyqCZA^N(-Isgc~G`9l`M%* z`hN7Z>Hx-%o(ug%0>R-#!1S~5NEzz{m>2wwZn79k)jX^RgGo;08ioLqC<_m#7xC%U z0-{%clP_K>^6xZ670NsfI+*Li7AW)BU2dPxACM;>!fNQr-+3Jl4@RZg{UF5ML64Mb&Hp<-tcR(#y6lzzsj$Kg43CqO>CJphY+?x{ zUsf&Z)?lOMt#x+~^kgr#cTq7!G}I&qg$jLye&Q+TbdW(2d`lCdH(7DQ_~+yzxhsfn zb3<`zOpjtK#b>?TZ7H4J5=fgpV8p%V+cnp1R%3cmp}Ns>SjsLs5ZuCS)pbI1U5I7X z>EFQAOu~r}38srP#CO&%8q_toy`Tj2@IVw8-*m7`bmw4*;j9^&4IV2-+)Qmynr`{Z zE2$8okC(MUPAxC6KXBp^2U>Nz9EsC7t*vIiHgB=<45HR?=ME(1X5TY4J z-6bNA3Mc9uTqMESzStKi!7*&*3iBQHtQkTrPN6Yr&&}9wA+>S}%Bp*|gXQ<9Sl55e zZOR$81E}lDwA9SB;yg`0R~SGH@c+_|dBiVT0+fazHvomsT=kd;7JsX)EGzR7b5eSm zIf3Al_LKm~o{n1vvhhXpN^u(3Bjw50+fBI3jLw|JvTZg9#mv3|}B^Ajb-ULFWBpLyRCC6;0&*4PwCpoZ{~{=eR+ZI_;G(|Naia zsih^CGoJg>C$r*vDlzw5c{`<1*qz_`juTtW`t6Y%%@o{Sc(<5{meqyZdkuUnL9lj% z?ZPX-yVk9qKSQhJ`ZaPV&3jQLe&l_)D|gWKYhc6F&5!AtV(c117sQ&W~au9 z^VQpOwu9bu9!|p1>CC-o=d~_wyRGHd&LNUDE)$*hoSn%jCoU5TbWEU7@UcJ&VdI+_ zE#`U7Dv>FJs=UkBCaR`XH5Dn9x2BtAX9lLOnK$#qdpDLSk9^~&3k*#uI%-&Y!F(jo z5aI1*J(vJfFQYX^xn!@Ja@Xmi{xWRDWOg4;!Xh;mGHq0|t6bXXC(BdwHWsI!fwj}i zPP!!%q;YKQrjUIhf4o+PIzy%LX4QBx(Od#0_iTh~d{iji$E}G&W@VaYs=W4NMSzwH zwTv@ZMsKcJ?0V>_HHOzZF!{W><+2C!(7ImFi`YoV-P6YpsbL|uv`kvgcQ+f# zYR0EWrJ!@>Ckm}+E=O~{o@sHz^kPK|A*!IcTW!X=!&7sVLg_0eq26;0b|br)jd3P^ z6KJUSo-?MWaA`XnaWX3ecsc8>fW*unTzeBwLq*QBW~Vq%EaO?y*pTwkyIxxa4~d%Z z4YmDnSa^ueniB^>e%KpW986K?Onp1-3Wzi*t|~hy<3E zdaqWCbZdDZn7`*rPQ|pQ!bgbQ4|xh;%U!vf9A69=4fI02pj=)hvtZ;W+X8M44Msi} z!_$nVx`b%>;_wp@C zLo5v6qLv{T!u^uIxMA5_6QM8CixZK$V(g9;0fP*g;yd4#f30|sSA3DEUAGyd+FpVH zyyH{glp>iR?ftfLgT+pI(E>;L{7G~!XEC;Wh*5{yE%myc<$wEp#(|-a#rRBgD$_XU zXW0D5H-alL+yF#y!3IMO9CR>ru)qVpkb}VZABC|8-YUguAZ5-Uf#g0YLox|Xa)uYc z^w!R#4`2t4sAr6rvok3w+N4|Xq36Or&%D7YASajZqy0TFg{*pUWD_xv(h0kJXoU59 z?~k*Xvc(%>1QIkan?7Wvwdqk*V9ONx8`PE>S6b{Y&yEr3Q;{{IWmE7TUIJ&&W%d@d zMSf768;1#J#~;q0^#KX@&?TMNY@q^Myq;}yDZ9(Rv)z)!jR3wwy&DjTp}O^)QY0fW zne9^i1emjr=Lb>!lycr0ZzNn=AR(=3fNHFpOTG*{SCXkLqoW=$>;x_m&`?YB^~@dr z1Q+djfZPzo`T>AaYjsHN_%s1V?sO4{u}Qwy;u&TAo`teL=esRYdf=T4KV(3*Q$dH9q+|pBw>fB#nN+kMrDl0X~2YsPi=N;g0f6j^6jgH1tPKmU7emhII8dv8guUc$r^wd zr-M@qkvmp0H1JAc0FS_8T^tM8ncMr;y)@sd$8}X{)Ei4-!?(!qq1U-PR+vR4%!!Nr zT(w@o8VOXL`Z50)03r`+y>I-;Tom~k$iXi(U_$9iIBRjIO16O`AVMyzdQ_kK>c47!sY@gq=?<;B;5 z`tI>Q(Iq^9GMPDu1>$$e-rVrwSropvrIwU5q~Pu{TuMLq|{EUR&olwhXr*Zn4b1J|&C9Gi1&#=rqf+1!%6TZiB2a6u8Or)0_lT>#C zrm}KKP=uk4w_{JjlXusZvQha?Q5}{8Leg@GT=rhGO2`)ae>PXR79flhpXqk9IMB zCW#f1>p&>o&GK>j4Hv!I$yYx-b6o1JaJpDJPx&15dq|&Yt*MjxxEkugr!stQ1ph-HIy>kx#p`ObBX!t4NevDUTM-6 zudb*^gD9zGZ*OGBx-}}-OE2oaYGVAC3SN~Ij52yHVL(E=0QHJ~xDZh(e>1HyQ}!~F zR*~2*#szr=YO^pq=PQDA!2@=15sn*7^&qv@DVX)QEsf2gSnObdBo9{vhd{dk19Bku#o# z*?j-dk~`(P+(X5@CElCo(NtJj=xBko9h2v1s_d2O1#lm#pp)3>JY32<;qHFf9Yyed zs)CUd0jWz7z_3Zf#0;}MVpWl|@YuH`k;f_-G<;|U31s#Tki9$%2L`~3K0B5;Q}L0g zS;+rw>DTT)3q(h45c^rj`SG9L&1ELq=&T1;$VtUF)~m;v*kRmU_<7Pe$fk00rwaUm z!5!UTb?a3Pr3*$$n1VN}#&1ZU){Cq2(LaIG28vt%I2ro#+|Di5y5e^!F5$V)c(4G` z>(FqUJ>6?ftD{D5Tn0~N4o&CGPdU2SQd{QBW7#XaoOOJ4uj%!(Xz4aZOK*(Lc;jxa z4Q+|7v^m>>?eune>mM2g5vnVex`(TBI+X`GVe0F0J?ZT*NY&^DCju0O1s0(~(pL2B z&D$&LiFfH4+e*J=*~j&rUQCgwDy%3yxDcrJ!0^36J}E>07BOqdHN0NZJ|2dZp&bx^ zUm!%oN`m}HsyMP#x+HI~ir301%8Gi6Dx&FcSn0@5jPg5PnDq&PEG#_4)rc)!YVQG$ zVh32e0NaMIjnf_J6^7M|=5~hKf8^wMomoWR33IpQxdwz{fPhED=DZWh+r~OzS&5AHcUAa8Hx6ty~_W1~#UtUqC`3LSJ%6 zNY*BI!^Y2E4#wwMLklkZH2qh;2@-RCxksQq>~nE4Z8%HUITEhD0#M4}iWxr0KizD& zg30)J?9B{FD=@;vHoDav8-CjDuP_6p)g5SQYUdGG-#df&Az!7Ic8v0}OsfggNte@i zpvsN!^hR0wt%Kn{`J6!8mi50uQq(_*9HskTA;2qZ`_eiw7p`=;zSe(!=4lK+vFjVz z^G-KR9^aAEIjidsWxf*E^_pJ1%{QlwZyCRmD0)^Su4hGZ9wvva4Zb_(Ia#97Y33Zg z+eL@3eeANcJ=9;cE3Iy|RQvn#{l(*#Z~yGmA1V7#9}Es22Xbg1P9Me(<{(FL)H%8w z!$)~MejGkNIP$3wQ9E%?;KWY)boMlQx_27RGf~Mi>uh@l=ft^rZaH_JXV2-`Ux6du zL1Yu1SSZV6+t?v?nO$Q`>;?PDaim*FEE0!%@Nk~a%lTe@oVW8)&gYN#EB+l&Sq*7Et-Z>lG5^-LM|lM`>SIc2!}&b--O9_aEE>>)z8JOg8QW2# z%vQM74#&7$YaFz)9D_#_pClrZ^H> z-(E?^a-CvL=OjbKa!=W#D8(B^AZ<)2XWl_E7a0Fb{FUq!@ad6WMV^H0CN2~@S4Vw> zD#0&RW8W;s&9sTDiYjEecvO73OcL(~YK5lmny2q#9!jmawA|~ohPvr%A{X-Q!TcFj zghnut5yd=4B3HdyMqOh9*)+R`9buche}ZQu7Hku$CVslMR$*An=H;Avp&EBW&|4^& z_kW%72`!! z^(l7L*~?t&2^hT8JCmtq97e;PZT?{{FWqAa<*t7cJE{uC|156r}tN`A5#Nnb5nuW6Xv>=DLisxic_e2+9BxuT$fpx;wUdT zFz{g$n>t=b^7%r!lXy~Gyfa}hY_$Cg@dlmH{g$zO8G}vOJgk@a!s9rruUVjKpX z60fSGXbvXSFIOih5Rc4?;Z8wc6|^lb(Dk&9;y;C!>B^vQ+w!5kSx7chYUq7_WpG#+ z0Kr3O@-M(AR^1xD1HlD4(@7dxVYsp04jOxVB6t!F?;vDy@Yz^*osdP)o9eQQ0eawb zu=9|er`{!oXDe0Ay1nkDfZ2x?ga-E+j(H6nieF0^tq_^%VZ~j*U_*F5UJ$Uffe+-T zfq|rMww~5&y8zbOo|;G1ec457Q>)SP?RZs_}bQnI&WLwWgD(a+Lj-{QmM;gUz3H0U-DCl6c2q$`ac!lC2A;cBgDJn9?`Tn zzD>|R=qO=&y7yISC%xV!+%vuhbA>;I^^YI*q4QWIIx6j1O3?pu?D{lS)6A=r4K0M{ z_4iu7X4x+GZyAbb*cj+bLBPO84;gZJG9LVrcto3=P4e{FA@YMIEma~-j6^}UB5jGn zk9w}sX@Q{@z+P5uYb5r#qZ0;;15&|(0!+pkN1y&gfQ%)<^D+Vuai;;`5pBy=`g_5o z>bMd%7egE~uiCZvPDLY-cE!V@Yw{UNPbBBel-0jZ@VHkwyPv8NN~U_t%YFS3vJAm9 zO;%TZoa^B3CipX+t~9dcgdnNGR}-{HkS!{epn&`lqPS_321LaS73wFtA+Y! z1i}E&+Ih4LuDx>y0de2mGaxCq6`%#$EM4 zRU5vipx%3U;oerk!u9XGAAJApf$jf;)h0 zJA82do;Ebfa58KS9k$ln-{E&kL-dE7rH*AA+&gQ%e~amIPoDG=NG@@wZbd%bh5vRl zXoG|W**4XuStVQT18q^~u7h=Pm!N5tX378OR&r1qLrk{N>e+!Dt42K{^qu|&!JctK zf!!f?pAURZC%axaeuWuZEUl_H5(~Py#Ex$7{PjK=khx%=& zkKI&NPyR!iTnsGk~!Q$g9;DN~TX|msv)4EzHB|xgVkq!)Ppal*sD6 zv)Ej`jbn<;GglDQ_03pn90j2Tw=MSz$~J_`s!zrm<5+fmB-AGaq})3PC8Prnb~KzF z!UC&Lr>G(0UE*ALfcoA}7zy_EoWOmdq3}+laZrkAh0*p`-uPt&u;I#{=a_G1+Go?b zsesO*n#ZvTa5!)+I&VVxc478j4zj@M*U#L8g`?$@(nrSTTxFi`TEm%4^9R~7<79xm z)6DXu7wvEVLxewA+~{~>eewI4ZNFQ-2GNO2!jU-_Kx?Z zrd_%JnLesnF^As?9pza1rHx`l2-Qqq7oYX7epGCDyZ9BRRb=326InhI>c8ru z$7@lIzjH^6#du=IMYE|?{lO$*A0FqFX?Jd9w-~*+ALGoA>4C5w5V}Il$S_r7C8L&c zCO+Fn65{>NF>VokPk_hO1)`+je5KWYWZx;l=<3g+ksFr2)(`1i;Heb3@bu7TJ#DFK!uuenIzJQU~#RumZ6EM}B*@YC{1s z7~m`&L0skgqkQ9XE&e(XD(@_*(pXj5KsqX?BJxKgyU7pUnlim0H8E+%-B&9E6WljchWt4S@IP+xPMWNl(!wDbrt(yldZR^R7BclA+quMLuB46~?4h1UnmNE>j&O=IoTrsb zbka>9BTUd?#+m~cghv3p`0&eje({(8cPMR4;+TLWB>TBM_pbmtxYQMZ6f!77hlM_7 z@VH_|&4lTyz4od_r!=dH_A>gnYJ1pD+p){`v`rsz>+aj59G%e`lbHxAx4DypdO3VY zMR6I(Ro==`e{_?C$L8N?@7CgwJ%RSHm%*sdf~_{E#q;{Zx4%jP0w;$PkAx@vnLW3! z_peoLXN+_dSRo)qhl~~}+;r0dzI?sb<69t_$A#==5C7Vb15ww?4Vs z>*z{`ZtlEuR$6YyHi-RtIiHS){cgLVEX3t}HZ^L*umSyg_2|-}-4&Odcg`87opRh! zEe<(q7X(roOG& zm9+vR_GHfhQ)0s?=mSn+qQfg9+bm#^>Se>UnBBAfLJACW?eA(0@#fGK&dJgM@Qg&{{T zXZ;qP8NW|MEtxz~O9wfW+u_>yTsznKdffnaAWd#E3xkjbbIh^MB8xRzVu@z=xlfA+ zJ*X99oH})g<(A{Ev`U*b*66U_Mx8d>Y=dpK*+__P057>DT)R#Yx^;`zt51wUBjQY$ zkYvh~WV3Eaam$h%_dHZUK%m5`HKm?;rrZlJRCw*RO7FZ^<%18l`s|BszWJ`!FTZT} z*Izr8GF{A=Qwk^CE)vH3hDk@J5 z3>BOek8RQ0Z3U&|u$bONO-zeB3Ok&&{^Es*`~_jS7ct+eS^*mdqTy{SK3rs8`B*rR zofxS1xZ#JQz|i=&F&y$4J0$Ibsug3FlQ%^b7fSSra{?ZN;*{}%Cct_*_0ra${r&U* zY>pacZt0gzP^TAFtKNY^3W34U-T`0>G9m*`d{`kcf!+XEb=VRB6D&Rp9`(DDS5Ijr z@1!xf00yK7_RgBWyvDNz@BuO5h(F;Eym&!Hcms7TsYYPgh2fExIolXE>Vi`mQh^(4 zq39F{lVNZULWF1mnr#~#HM{`cCd$DlZ1q*vMT|fUA1Xh>alGTAIMe`l>s8OLmO~z0 zfYq>Q!(6q2_bbpnf!K(1FNjY_k4W)y**ff?;>fg{frkb4j!iU)2=qeK%S-mbRR9O( zw93G$2w(gqVXY3Rc4@Dx%JPWz(Mo+A&+*ts;QAm!lBf;ThU#A&?pTxo@v1zfH%~sb zivsi2r6wY&st4i4!H%u~GS@#y47}(gBSlT6(RLbqpiGPQ#l;G20$)!!c+spG$6xA% z_=ALu%HGEnT_TwxH2*dl$idAMZ}f=Kh%xD}HMkcCADNRr!OHaVhY}s!?rP&rwtduM zJ6kZ=|m19yo?$y?J0Fd)(|D-F%)sYO)LE z95&AYJm>0Smo^Ns1c?x&7GOkPyJ3KiNTn6r-%f%WFlAF zgPLn#2qW~T)O)}G=ePDC?y}9^xf^;->OeE~03iB}VvH3Y3^cDIb^^L3Nm;}d<~aGa zS*X_GrF!YDa#doS|!vh%nSO>24pdZVtdz7Z0mrK*6pKv*?=y+_YFOSij}YAP}}2y zRGTioS{14AsZ;*WLAPGloej31TF|rj88Tz8;7(T~ckX&tw05~`)-^=Gr4U?VJh&IZ zTeF{Yx3M#i*=3Y~{nq;Y0{C$>;)mMs!zIv26VimR7y~CbY#4JZlsitv?kVxugETXN z7cm~Jp^7Y_!MHlnGpfb}-p@z_r$$#`t0DmCP@kdW@8lOUX@XVE(A;Xp9clFYx>DBU zy*9c5RIv?>Y$l{w5!1XK9XrM`aKK?^iZ8dmA=k;L$&hdvDadX_*a$Z4_!STt`;tJ1 zPwPl-Kv%^pk8ENvg;dXE)_s|X$Iw~NHpp8mO*}e$pRF2e4!Ei_^$Ec&+ffnmmN)p# zN^FIU@E0!0h}D8XgFP_Ts#A`MQT;b^5SgELAKAi=EXQ_xY-lx9i;>C3hb(zxlX))U zXivbP_S9$ro~quH+3VinlD8)L3@zn{rn#M@ZlnYaxFv8dd4W5ZL%Wf&BCUn~B#Dlb zWBelk;o8n5IT;5_SydUB&sYte(JL;)$M}64MH2{I`APOy@3YLuAQjd93}) z)rP>CTz%D@7bc033*us|v5U)WPVO|4ZU|rEy<>;k5_W+Lx7qC&A~yJ4uarlvQk$g| zHA7K;ixA&DXg&{iiYKbc4l63Pta-hXQLo}4Y7_R>(Eia-IiYb-pc5D5H!Vj-M@6F9 zFCJ)%#zmA%vLSZich7*G%+5sf3_f0HtPQm}L&qXy~YE-phB+ZuJ|vPP`1rlg>odab>BfXb;6;<+pa zJ$@7>`7rUYYnp789LV~H^02t0DG^P_*{d3(R1aN9@04%$h6w6Ho_B?lO?G@{7i(${ z(NsWGrM;rI_-iX2&>7l0nge|4M6qt5{?@NMkf4~@@m_8yG|}! z%m`X!Ti8Pk*vn}s-8(ijIhPn!@d!WD(?~YQnewX%tb)udQYdC_se=pK2v)mDUImy` z4@rovVyzOh!HfLC#zl8eaLQzxjs{#n>r=B*uc> zv)N+Bmf-tr<0T`uR`4XyKHfg1TTyR7cgcm}%!96LOahgLM=BTPl!CYwa%;m+o}}Y+ z!WfXo_cQuq;H9KeVi9%Hj$^i|#X&Z7U%aQG&43FlFJZ6tE(ahP(yBo~G*eio*al0w zV*t*;;1bFEpZmj^{bX*8jn@bG)w_l0#|UBAA#3E6$mt>ZwWPA_Zsf${fp7ykTf z#D`8z+qt%Y+pBc%>{AiLI+}QL;*Zs&EQO7w0+)yazso6Mt`}&e+GbY=mbr8GhJB5oMK<~TYx+orQImF-|QW`h&cpa|LC zQcGC9qLeOS?S=KbQu4VacVvrW#AkeK(<-8#CHqR>=JsN%tEYyEMObS84UwNky~{%( zoO8{#u}BYF&1i@=9~SGM!XHGOLI-n`H;Q^n|KzlyVoIY|_NmG(*ScRwM+EPle z$OPP&juQbznsfF9(EIfJbx)~DOt(JS9ajpQ~Ks(rU|Ppz0j?yKmpr9w+LOm&(9 zxe#uX`x+NttuUhVh$UIl{>U zsC7Gj?f>vY_B^BZWyd+LabR4d7Zu?G2^7cwZK6UM>&PS~(yGisu{P%pbaN1niS+pmMeGsp47r>T#saO5*^3G(A-{) z8o9D5VN<`$SJwm~P-A-^s1gIAUk9qHiNRNoI`Tmk#MN*N8<02)&WG|E`>+L4)kM>v z4LAJ`9)lDTunm=6Wzm7zVa!yhrtSfm!#D3pcM)i1u}xUCJIKH58QXi`WB2Fb2k}89 zmWLe})ewWG%xb#I(BFm(buL9x68U~}zs*L;4`h%ngsPZNEX9Kz%vi9q7RaLr%w-(#> z+&m7e=n7OeU~1)ZX>Y;cPK|`Jt>%_el9lw}97J%!rF6hFw+gO&fA)JwFhOjFt)v4H zx;mavTSbgFGw*cGT>~n`M{G&to!r>)eo=q~c{c$=~$O9{&mPyr(`_TZ)`U(yAfK|A_5Tg9d|C|h>egG>#)W6&f zw~D)jQ4k-yF#woTM~oy>W2#Kuq=9!e4ss4?P$nUm0|H?rMX2cJjlanBs!gb+R_@B)4YZd8>sT2ZDOWk(zgsKHnu4{Jcgr(MKErw7JoM78fwQi0+0t2|20o^);Xuv?rKlwW&Yxly!pl!!D zjXHz4dT?Ch^8XE_MJySK>60uOWJpq_IctE{!a&%JOmdiatYg28#P&mva%_(|iILis zs|`E8C>sTf*aB%q-E`V@!L}q}ADP;;(N&D?)HH;E)qb*(I2@{m8&-(skR#i&HA=|y z$P5*O8?*DOg5n>S(pXzGY%l9w^hUk`;{24$VAnsa7h+V_#Zp~0)kFH6l5{(%`2f&k zGu=N7BA}N^Tj|kK{5K=U_kQiKk*#$d^0`8SsE(@j0|W9Aw3 zq;i%4OW6q6XvP-wJ1ftN#fZ&-ja3Y)mxl*%3VHs`dAaa^ah5o~4~>(jsDblR)9NB7 zdb3|o#26|TuXqkvR)pQyZ8mt6X{$L(JZp_oYh;QSX4QmL)Bw1(=0KZmb`HWC7tTUe zC>AT?NNtWw;`Dh-cSkRHgSk&9<&ubLY7*JQPx&|`BiTe_g+A3j_815yamrl@)&?$H zsrh)N|e;lER;f2*j6T1cnrXuBt^~Yy!E}h6-9* z+UG9r`&S5YV6r-wjX9mqwAJz)MqM5ga@B!3Az1(&nFB|)%Q9J4=b3=8+5P8Kx6A+O z1qUbw>JZmeRpe6Ru_zHw(MUHe!h)qCWK)ST&}qs~C$1l;DF$d?0N%6~`QbSAKBDex zh}+pz`uE^6jLhYZlp$i)z*g)Kz1ZgM4ynQEo$=V#yCk66T5E_bOVtM{cKJS*0y5i! zX#XYYMD4g4YI8aw4^nF&Ge;$J?Dia)=hACo(E7Q(t;LxzaN$k?)-}kJHC7kKoPt?j zy58(HIwZ|6W_jRpq;Uk{;1HNi>mKNFuVgw{!dHvt+yq!c4amfE3aHsKWRfLOw49!)#2tneEO=-dmi>s46@dCgMIcLTp0cfd8c-u zcLnj~&Fq4^7I(r(bK5;t<=Be`qnNN1Kx&8cR9$(XN-nJH8X0gV4u1r(ZIEfvX8@?W zh?QD=JR@wJ&sQiMK-yq!$g}TFhdh(6ntAm)ouwTK>g~lC^WSJEU0g|M*NXuLvZl`z zt>#WR_n@I5yGRLvYFmgr-xv-3$eZ3z?piCz#A;%d9rml_gOzo5*TV8<{AdETlUX#) z6=Q|H1pt#6qd!;Qv+$#zCA@|~w2D@||%Zj#UT)l1(K`wx2|?TyxlfM7{=O?J-`O8l7NSqn#0& zeyaXTebp)WU0e-oNdxnGVykob%}Hk()|w{PiE((|Rmk0>$KJlr6}j#Rj9@i_P?4cz zpt}iBjdN5%!eV%RvU~+tbF-=4#QGjM)f&t1U}c-LH?a=K5up7cVjse6PJXqG$Szcy z|jbQfz{GwXM7I)%k5_E5qY1Ak5C!g7Q-D;<*v!g8qiC%&c; z*C?U>%I6hY;wKp+1dD4)8C8YCgJ3U_fUcN!pO)(vE2lO z8EF?n1%9T1gBS%Vs0KJ?tMmL45C{kZzIkL2eI~+G)0Y2(HHak(u z?kIS&wk_N4f(c`U_^C*NGG8B~XOg|^a8ltp5-;bmH9{>*72shC`9>_db=kRytlVIY zA-eXqm<OPjoYuv#M_TO{1?iIriffeK>C&%!2=%}rDn zt6gK4o8&|;CI6W$`#23&Du}tuuVG}v;(Up{&yPogH-DLL{`~~tvTihsht%v`A6sT! zpz`(rhj};xdwYRzk$n>M=APu;8B^#;W*93#Z#6V?u-*|%6Ex1pxmcEPS-)Px>7Y@` zB$6;!enNO2nBa>@&|Sf7X7=qz(a#lBdP!FlFkJFWkI3v^loJYjVk8=lHP)k|<$#!$DDnZ)i^nPx(=P z+idQK8%lS)E0~6F>oW*cT14)ac@#RG|(=; zseGbqLj{!4DRD;Bvy{iL<8I<>Ft>dDm%`$)-7-|1-qM0#WH-H5^!n2$*Vh52Sb3IpMT0_!qCslGsoUDJQG(+?A@*oN+(nzL!}lVGNY6RNg+3b-buoz zdQE{Wcvh77J0g4utNKgTd|Tg2k+Eq|XA4J6BNH|oeruvtEa$QoIG~c%htRI|m4__9xH`RtMtsgOMLK5L}stv#9ez2E0&hB?>~!Bw#EE zK~oG@7U!`QV+yUwW-Id6j3xzIvY6!Sih+{emBZcu4xxV-za$jQs{lsxi*8O;CvUX9QkSIi1F{zB zI{p<8Y1P~)k^o(Xh(>AmX+2>;Gz;{Blrp>spY{3>>Cg_nLVd{`Tps}iR3pJ0J`1Y< zXdxJu(Wq`~Y55+R^aprEIFB`%0{SSKVh(VR>O)0kAE#t*&ZgXjo^3D1N+R1g`oS3uMY zlXdz2juB8*vkE}At$(f;3s+(7Bc=q;d=;2Vy5a~SRLoAQ~8^-RJxH1^>~}A z(CIFPy%QDy4EFnUZJ*dC%MRdH2L$Wm&<-G8;7EaSL*B{#kK z-RnreaR>YcC}qP0{FVbd)p*{#B`hOR4aktPq^u(lsHmk(bi~lp^kR(+CR$T?Q`d)o z26=h1k75F@#uSXx2%Li#ae_~tqT|Y^I3=oGTtV@Hz7AJ7s4OohXbY@j>=QTl6b}+% z@5>Ie(TZj)Bl`X#bt-XFvyN-_ zMSF);VV?v(4kCSSm}<&A%YyqBem_2chr~6u5<<8Dw1AN3N6U@9lFH!XMC57)*HMY9 zZj)L%CPo`nE2HQ}Et)3{!N3o zl*`k8!db_1_RKVe$^uY$ID(MUECJ;<$2Xk0;^M{hy9~eAtyUwarf?Jmw@zb+TnR$m zrHI*9CKIo;$F1xul)|%69h-S2ON5NLhpaOTKi`D|B7=YZ9w| zv&^Yr=;<`ZvU?j?=b!ek*$IRA;@nI`)e@uAEJc{xnOBC$AXd)*?PRW=owCrmAVh(< z9fcg&E^M5znFrquW-=JdL*=0X_9^T}%F>4od=O!UB?Fa!y*reN7KO|KUVSB z@!v%@sZ&EMjz&LOTm{9b5kWs2U$1uRV!Gf=D`ocPwzrWC{j+HZh(0~_|tFJ~&LOmAtAQD`SF8Y!` zHxqsaozKr>6Gm@)^vhWdL5qkWxcYG=rd_7@=Si)UwolwcI^WZpHK!C2$Evog$6RWK zAenwfRZRJ7A$dTRmsr^nH0fhKTL4g15=1zOU#tOpnj)(LtD7YjliETdr9xwe)0?3Q za^N|V!F=-a2KL4y@w4cSV#1mt*H(M&+kl{pDnWCDGFKoqYibwmqY|^^$xvw83btO^ z&#tfGmPTyW9xH!08SWDX5!x!n*kfOYq=oSsd9`I1sZoZi@b75kuE}16+xOQlh`Wh6 zesGd}!Oa>pq4xX@57$yd*B5PKIBt)^uKy(B+N$;FRsZrN%FEP1#Oq^#_M42qHe+9Z zlEv>X7`|fYc2-ffiTAL|*@K+d!h1=}KLl5ZD-s+;$tCpjw=W}Ba%OQ?4hb%13<#9t6(?o-r%gIMz z0odW@c5tJ@HufXjxSvY_)4hYHRPbMk?Mp?_s$$U$}lk=>|Stq3I`Nb+Oi%`%f_tJU{chR7JWCFBjHdg``AwG zm2!TT;`RlOzTuYO5_M!w0jPLsA@=xB7q^_@P!`cLs0^&5)n2j81hq&4MXwKVqBnsA z3zf1yJb{sXxq)7Fi{;PCb4xVayNfNjMiZRVwYXDHtKH7xYcu*zDy?!bjAx_6tCXvu zsKLB;5Zb}81AoQ%iI~kk2-8t2JXy_|=>P_?oj|j^xR6?b!qY>fYT?PmvJt;*zS?8~ zq%w)DvR<^J$CXaJf`XJ7YNh#I%2X&yD+u-P_K>2*iGWvH^DI}xJM=U#;^RV;IJfaH z*;DuiD;VREq&rD46~ToDp6g@p<1NBFv8;X~rqA@U5f|oD z-1)wsf0NBYy7e97gBI; z*1McV(^F*6u`bVX+N{`ZSMVEB;x|4XyeSOM78TuHQXQVEi2i2UPS~aLVlk(_q%FA% zVmY*Lot#{XArxLQKA0l=;omu7(t|*&{H6yEzlLxSKNV~KGRvx&^9w9?(kFK_$LWrz zt()NuvJh`@9QxSLNYttfoNlcnB`xDX)MfO;IYV$m4z>Ox2*nv)mc~XaaJbFCK=kr!|es4 zTwoyi0_>*==&{cT3Y#q#+_X>!8#RbC`=r>~=QWLx7iD*R85h_jvx#n#$3XvSf4tf? z80XCo=yov-jMu_i2FU!;`fxIfIT$%E;c%Q~fDn-wu{lWQMi&zJ(V|_cwxnf!nl{Z- z0_zEcoYxzeS#I?z{VsJ@KxrTJW^BX8*;EHrgkpmNWJA~Qqx5U>`T`7Dya3*oTrB1B zkA*MslP&f@#Lwms?mI>EYtwpMwf@tTFTN0q961^#pa0wwkG=ER$McG_dV){3|7pt4 ze&(?!-uwKMdhCVc@#xVb(KV?91<_8?(i(h)b3iNYrPzB|+*l(%dc#%vp9r_P>vcpy zzSCo}+t2i~A#8tAE$KGEWx${JDXG9e11}D0Y}>vT&(FwDxHsof>#5U9L8kHou$YSk zp~2i+gzndaIbypZOF#fZk@gIMNN+43GA`%}*WO4ZQZXuwzM(`Nr@;pPAqAcmxuASx z&142=K^&Q-D!C3kdU?W2USyPd($`5|cqJSj*W!UIzO*U?BFe2?M?K`H2n{wCoI>kW zapf|R;<31Tg=Pb!l}fuk=B!+N3?Zft%2b$&tu`Oqqh;*uEwML&F?--Tw!w3;qA&f@ z42;j#o#LV1VL2QHZj%!~WZYL-ckG2?qF1`w6kD$xII|CcGLl+b!Y%TsaT0yrX8cwL!Bi9kEpfldcdt{2~b(4|GWI_tY55P zNYL6y{uDz>r7zba#a7y*_J9v`p|tMA;CJ%>(u`zwNCYY1M zN^$TB9~D@(mymVjM-F9iKsbKU-p}Yaw~0f^k{lWQx205WE1HrBKc%p2L;E?g|lQrXJD@}#>vVEX9SodHo$ z*h7!}vl42|l=4uAt(RxuDB(*hhixKXuKPlWf7!jlUHcBzo%Rjzi2dmPRd3#Qnx-d9 zwcj$k6gjjm!Rl6z5d{3_gP(12=+HGgV~`)x+^=Bx1gEHDnX3QA|Jl(HXVvcDnj1bE zkBH6H{S2DCZk^pEa?0%?;|w``INefklSQi zk;q5VG!3~SLXeoA`+=(*C!j}a5TXf#twxdW1-}4Y@R8oCT6J@PJ>PyKyM4%8?7*Uv zTiotZe@bBfDH~Yn=oy>_FW=h)gjw+&aWKaO-G@_(8ChPqMLZFhPdavq%+QDTvuLDU z_c0q$0R`c1BZQ0HdfSi#bJ3|8{JuHy%t8p&0ftW=$wNFeh}FQ(%zJs&x^#83OeTUw zsG^gjV1fM%D{m?z^iKlaHmV+e1c7wFL^hJ)VC#wkDXjI);(Wpw=uLh#n(*TvkgaK{ zIB)e9DI}Id<#FoW-3ERQ0mvn5#bXcxJUP8cGo{Ttxz$W&TQea%8DsQgu<-eEmMCjz(5|1Q%|i0BfY6JR&s7Xqm6V{1uV zLU~WRB+!p5J0~byW3!TMkGwHdUMV7GuCrf^}zAH6)e;ywnK{Mk5f*hGQG zAnnEh!QogN0hQn}$Q4NGg!5`%u8IJrdFtt92yqM@(JmlX8bJ(YnMNrnAoSiYc)g?i zfhuvGxlVjvpTA(Veix^~yTI}xrT5y9wuo|dojzzPFV|eESpEM7h25flSV}?O zyhH#|7$n7E+91U4Keg#hega8IPQ}I}EK28*01W?m1pce!gT|$M@tsnlnUqIt{wZ%9 z#7wOV4OnW*;*J8waP*1(EB@H_9fB0y?w6BGZ_Yca@~W4HKlZU)^M+ssBQhLfAN_I< zvbhr+Nw1DAbpOY3A|Cb?T{{B|gYC$xLznAYFh!ZsA4atRHw*--J3l_4=d-CE8I<&K z$`oVf4gw*w;J$0T*$1&5+F%=OmZ}~KYcmJBUnFsA(_PY7ij2%Z!rE2J=39xtC4bq2 zJ0;gx-pszg<}R;}$96+B4p?WdIN7#*UJ3p!d%Q1MZKgO`aG)dOf6qJ(2cdec_FVz| zmKLh;FR~oUb(dPd3WP$(1oACTZ_N+@eTyF;zMnYdY~O>lB1$FL?5_T%fUgi ztQWD0-ejwHupHZ}50dSt>DNz;KiV|PyFfhz!g#o%_>p;Ik)Ggbe}pI#XQNYQ1sGHomL;NP((C68JRtl82rqfaB9Vj#tdxlkD?j_z zMPs3^n-No;5i49BqF<1VU!&B_s?0Yc&7iFnG7bsd+teR6(PG~P_Pk#xKw55Q0_!{5 ze@*!yr{H^9u0Eh>lF9+df)!kobTdgZKCPfwx2o&Ar)_^(a2mWUop=h)sZ=kM;8z~R zk5ZI8_l^gnDG=J0Y>rc__#jW%{XeGC!SOPTLqxx+v`{p?U;jvwC?{v?jQ-3hoJ*H*`SX&-O=vLf&9c*I2B3fjS zJ2sES_@QG|@y?vRFMCH;xA*1C;p(W zDbd$45}~#p7K1{b5|IqMAY0>%t87;#Pl-QEM%_5POPNm)YG+$pYzCwvTx{0L(kH#y{F1`HO5(tOP>#0HYUk-DL@*#HM!OFx!Gr4;|_S4Aj0qK}I=)o|Ao|Xd#8HEuV##zt4xE2Mo0o(45 zgwb=$jb&osECE|N8D(j-3aO?#H#nKP^Rw?9Y98GvK=#9zE+KbPmBK}eTOL3|!^M@u z6V<>{DZ~oZXH0P1s4eFQ&Z%WYJ=0?&P<-RRmW4WL5y?;c`j*sCT3;6VTZXLoXmCQ> zc2jU^oW9H&}PedIhFQ?Et_>*!+l;hK^M&hO7iX!a$&|y8^5=i_n(i z!pk9o*)jVb39jZPT-HrT8_IsRIg{`iCvEa2y-hKya?bnlz|^>}VlUz;n?WZF#{B{X zom!d1df9DKS&7_)UjBmR5y5Q+N$QzwPjL|}NfR8jvbq%Y12w2Msd`G{7pwf5FQSjU zR9R<*e?h(*K{2=kZ$%kc2-)7_ESz@V{Ot2#ntpi_&bY77mLH_R#>0GPg7IdQEqT;3O6M%)TBU(3~Y2i;0fHVd4z`q6YgCnhpo06D$8QRjKOU>db8 zVSY%#N3YYOzqJm>D*Alah83g5_Lgi&CS-So3zN+0t0Y={YI#N zyBCasX14^SRDDtlW@KU%Wcf#zwWG(0$l7+u4AoS#VV$SNKy4*>uX~JC7J(gq;9O2J zR%je)+HFZMv1ne_ewK`5)nwErQ85yBBMsf zRInFL(GkJLG&G6+H1Q{}mRApk^<}hMnlysdO|F>C@t)w5F_;1%$`0Vd`-mHS&kYjf znBGn_$ApaNd)s7-x%THww^a)7j3}GfNfp_yvia0{chBCw>ke64{rvlTn|^(&3{N9! zYr0R71Bz}^mkyB)f*cat>=8iA5}{>wkJ>Bk!_wL)FCMZtYx|&hNc%O#S<3s%XTHM~ zQ!=u@SUVQu<}x^urW>iPa`?pmybP1nKxrL;x(O&wXi%6^@!YUbvXDHFi9ToUr4Vq? zvdnXSx_%wB07Z_-S?6(!sRL}{DgLU!!FYR4R(IPXCG;ZS{|_tyifiO{`2-t$T{h!p z{kt~xiawh}Hz=6{<<5u)n*B{Ye$wDWjDzA%^LXeClNM}Dq_A%q z=I;Thpua*TN}I|}nr={13cj~WUa3_mGDnbOAy`~oVe1)z*g|-6$~e?xyg)hn+aj=b zKViYMVBpRQ*c_KcS2}2Z#3i1d;nj#^-7+zW#9GGmN!2Ypb`Jd?RJj8E5)k~Q%G6UI zeq*bAee7+0^GkxVFQ>=zLK%&c!6mjH@v5H;QnmU^{|>$uT7rd|!9#Cr9=9Am?7f7yPfhEp%?SAQ2Hv z4Gub(c=|?kP1qCV^^4;UK4L9MHbE8wMPqaav0~~VUmk%e#1b%SX$|k>>jLEcLw)?+ z&+MZn!`~$%DwyxrzdlGQXZqdV8JS^=`=So)?Jds6!I#XRLa*$tg(CFxa8a+Yfs#NC z%lHZte$R>n_Zi&zIK`(`$2&yq(XS#1|G9IK^q4#~aUXe}+q3G^5x#Rar7`X#Z?l<_ z;DahmL3@4dM8}AxHI`HMHYnhDNE6*=pR4}j8^#DBqkK>MqlFIu6^N)`wYTIf~u z4ex7wy1LQORV9f16bPuM@)dZ?9-W2AYT+y^2JEyp*X#3=2=}m`Ya}%*)etLB7-<&S zfHpFXmChK$`|0UIxh}Rs0Gqq350f{3Fs-EQOgk%A_0r`j;ScBo#51E1fQg5=X7(NS zv~7-tbl2Leh6~2yTfLdziOWnD$pd)NbXswchW-*cpML!8=_k+flkwDaBx{3qz_-fmbC~Hpzlqb z-j+4Q_bs3lB{oJk2q&a=t+-|;t;D{+)|8~;7Ph5c18Z?z6noj>{$}CktGBO&SM$`n z%3z*nM%$kEVzMDedoF$o`8C9ec2b%#vP35|Rp>BWiS^^T(lcC)ClFg0!t_+{C17iH-rFVR1R$)Q_0zdG(G z4`$)}*>|E1ryZ8uQk3aA3K{0}Kmdf11wgt+ZW}9E#B0mAB7rLJcpD+xx$nev{%BWn z@j{OBZ-u&+vx_9jKZWQjCF4?i)=rK3NN_@lPwo#$PE4<;YWK&a)csJe)oz40dv}xj z&N7VZ{h!Al{xbULvPo2Vv`7sJ#4j1ZL&FNYys`Kp8K1QK!xs|I5cP8gaANwFUk_dq z4&-lj3IQ60)?C|Aeyu@aRB564jWrsZ9*?Zpc)Z@7B%((n0|Bq+BeY!d?FQo`r!@4Q z(0{m(s~Z(z@^HX7yPw~VJL^cIDPt%Z2bOx#eInvB7;rZI5ykKT6&H7BTaL&do3CX#-TBOB7wG=0DI(!rPBIa> z@PLo4d$FT5LS573ZahqDBrPk7Yb)tO2f@4#&1?hU-59(5)yn?fKQo&D+0cV3@mz4I zA1pGhHVeu#4$p-kDep*Y;NSSid5_XDiV!a-wNz7E^cN+0ls%X55)zVlB3(PC@eAZ= zJVyZjv=`IDF{DMCXt-k;nKx0cPx2V=dxWB=)z(ir@1HKFat{Oy;WG$xVgOR85-m{? z-Va{JekYHrEr6dr7iR*_wL57k^H9W;5nT_#O;B)#Cdc50i%3D)=IlnQ;Xw~OMFxI` zmjgkOqAi{Pt5g}+&<^&#m6o4i6GS$p28?1fv4)!e&pNFRdF#urfT9@gK{zA_Ac?R$ zVThhRC1eXv9t0%ShS>q;^VV9!c-bM_Qeve0k(M@^)zn2wH`paHG}fBsDTRBphZQ3j z!yTK*kMTsyA@lWoZlimcw#@MG<3hL~&mU-;f2tl|MdOri(h}%O@Od_8DJh%1%(&Aw zZHFB;pI$#-)Bx}qqnDBgZNs53ZRA9JT7v>5Kktz z08vtt*Zr;vEn8#V#OZTW6tS7iH}rzC95$YvtY8!Mp_3M|G|U7#_P8zpEnJU%s{x6~ zPNI~V1@yKq2T{1*lP*i0q_5y;0+WmaQeFEa8NyDqZ%Rn-`l%`0Zhe~gl!8q=f|#?x zuq{$GqsxK5JqT)K{i31EJVPcONMS$g$;>e+YGM6zj^mG=QZ^xE6k2p_v(12dLbl09 z+;1^C>_a zKnydPgY1_#>nXBT8U!mMZ!k07CALSVrz$6N1TN7ObvVO zmZWvXHTLB#X1p@Yd~S+KtA6oJBTF3X&amo<0BiCb;oLS5;Q16XK-fs{6tbLwDd17H zw^sx>IFARocm{)ztAM%S|8rA5{P4rZnH@I1#vw>Gb>6K`Xtot5c@@}@fQV5*_; z`IZ1xNf3lAO0~60JYh|3aRvwtaoGVffkug)vDYp9G(dA5o>4;Tz< z_yBA3d*Y%RA-%C%=JPLkoMoQBWYEfH>YBAN3$>pr8 z>^U!_{-Cd4POpZ(8G%F~`mDJFd2mC9m4^@*zXN?25R*~;?8YMNQ3oTsOrMzrjTLKc zGPY2V!`Dn2Z3~mRKmd%$pCj|Y!@QJ*o%j-TSB~d$5qchIG)G!zP#}YSxZbf(LuAAb zIh*BEohdjuYl+uaKe22ZufR)NnHBtIls~s^wr1&brPe)sw2E*qz8E(MfeWeR={*Mw zI#u{P(vxBPQ=nL1lILG2prG@zjY8rECT?@H78T3%_27!bXnl6#PTL)dSQ~6KR+=x> z%O)14dS?^$$*ajoY~bt((82FOmtUAaa-x4N-Aw1>cha4C^VoT!|ga6-7E?#Lg#?L2k$64;ukqR zH(Q$%B9yFmQLnbUd)WrB2hTe{uQ>X7prcdkw$k$wiC`|G+H&||Nl>hrN;kJaRSgKD z-Efp+jx1?H^DzLx2{#sbE$rjG8v>?Gf<3lSA{%khlq`=22KKrJy>1f4wRc%M3h2Vu zzku-Fl_PWyI5&;`vqJzc!Fx^+bi*Dzzvd$prdergG88OwhIocF*J7S&YP^YJ#6Kr_ zsRFp6=3Rg1YPIFYmst=>R6`+0v#2!>+Z9=V?jK7+Syxurs#IbGE<_~4@!b53ys9#b#;d_e@e%dBW+C&&y)h?U9fOz z(xcbNkgwuuv{BE=yW=q9-aLleh=${1#D^nBq(fKC@bHHPRID>@cdh+H{9W@(c(wIC zFrMb6p}RXOEV#*s7sEW*MO-rGH|BBA<|Dm31NQSHhm zJ7fwmKvv!<7}iwrddvcoEtt}_J){dsAZUx=XC9Lk=B0uavJ~3DB{;+cMY03u4rHd& zpSfPFn@H375gz0L0SLPTVSKC@&@RLmR7bXBLP&|<0HS&SXF53WcoNQJDPC4Lkl-3y z11e}Y4d?RR9B2WhjWuEkf~5KHu;CqvX%qGnfkfwKBA|uq}Y)&a;d^k z=vV_b>3MHCh#2! zq#W`Oln3I93VFd_cnrT^AL`koEwwz?`L^|W^Q*S^0lq%i2#qv>Fab&YiGIBfKifdj z)pAj_v!&vu?5TpIp!O*u$X%dEOaOMNOVS*sDzth4z!Q{;Jzg5v>KQPQB+94jJ1O|em8Hg@oq>_6 zkTfc=-Mbp*O3tkPp}vtwi9ic_Q#Oi z`O{NWYWZ3G;d>_1D=(7|u1oQ)@Wrb-G8e9>P|n?myve2A%a*=h)SaYd-CoyNX?c+p z!j#;(xJL!q3u7Z9nsJ+|dYwk#{s|RdUfQ*b(PAE$_S1!wwD>0eY^p@3#ZSIoEJ0){ znPRrHkdHIxr|O0LG?7@N&qlzSx=%K#m%b+}k{r z{P3-42GL7eThAV)s7119EWE=O$h8y@Oj{+-0O1ey1U$c8NMW)wJ_zMBqOP5&14|@P z>|>BH-S?VyFgyRkkR8kgRO0t4XhvAN>d9Og*g}_~Q8#Ji2M+{#v=82u(mYyw6B%wb zx!wcze~7<}QyY)T1=Xl@R>C~DIZ}sww-0f>SeyD8jQdxC-5fFXNuAc>im)IPwPZuq zogF#<%&iC0mvO=^2i>)${t-fpz*yVA4)NTMGvPCb>g`QC+Q8DW3?_H*!!cN?^(k4r zo16>B{t?hCAB5Euj*k*MN4OM3N)Cwg0Q&6O!sHhG>S`*mpU8+Hf+`Gb96M>NJ`#4) zCXFO4fq0i}_)Y`)ib(Vw2-#9){0_+AbpgI#$woBI>3!nKSkhJbzw!aXsXDf;y67-p z>Xk;`2h;=mRNXp)8wP{-bcW7o`AdD~n)*PN-IJ&9)c$4kQ6C@i5v02aK$_(Jd{=?C z{4!yDbXkgJYni1JZlu?9#yjhr8TRC#1Yoj_?uWkXSdNVr#jt*GxLc4&E6H!0=XOX)=rg>t*`ETO5XNv&I^m zK;MFq>Ydk#EaH3?jHi`A@P8V?qMjYklS{~kcgyqw)t;;p=frmIG7uF;JkfrSojP)J z(zu~`I5(T1PNMw?J8Xs4Od2%JkJyvq!@6_$5*{OiWP>Uux=K1V)lPDms?TSR&H@u` z>*<&g_Rdb?*QYuwbH?ePAF7wWLvMW|+s;)A10C3z5DAeEX=a1$CG$AxW)ihYu#v&d z!R)n7fiB!CV!Cb@(Ms<|ZkNOZE4_4%FKgT<(H-Bo6U$d#DB5*PHfV8Sk}5lgfArF_ z(-$OJ>s)gb87CvM!Gb~H%jxkHNkpQSV1z3SFCILYd?R=c?(Od{YEfQ|amkU0FsFfy zJmR%n^1BVoc8Ui09V%+?}-164tm}vc^g6J5WWmCcB1&*4~On^H1 zn4G6h1uXCo(Q*hOheY$VWX;d7doL!6oFtEmncouf;U`O;oqn-=OVem^$loq1^wYU? zRnULS?Gwi(x@pIbv&(zc)KkCO@5Elup538_Wjue7=gU^twYF)|4f$o!U{(%fnXp-y z4Z9BWkk~YpQAc1FM+`Gr%$M2oV%}|{rYOJ_NIouy`v~Cqn>_5ObRwST!Fwz&ikM)6 z2%ZrtO$S3@>4NT?1M)(%D)h>ECFt*m#bIA}x6c=dO7V7qLKO?mXKUWk+~Cw*mq*Wz zdW^;^>M#FyC#VSur%`FKKUXsYZ~wk<3o`KJEb6egK|F6%7e#N~) zf4|h;z9BFVdVyd2y4OL zlm)h!kf7B?jaQMO&uPmk!eNZ?$?u{V7?B$Z5*G0ShuwI&nj7e7tzvGP*++?>2Ul)&Xzo{-oBkJ zrUaR9=$IhlL-H_gUW-iWkY2aj% zs3nN-nBT;49EPmU@f3$az$N6HLZVWXcmtWdbAH{dVU31T{*#46DwoUV|1{Gc86Kka;A6#LlgaAU^H!Y-SN^@CVs>O?c2=10Rn3S9 z0Y0L%!n1w52Zj^72l503omQ=iA!~xgVrG3u0~S}*x0_rC`8M6b_b~qd_>P^Y;iHn> ze0EdI3-75mHNQYD01+4fDdI)!<}i=ZDv5#mB#Fm77=iCv;BF8OroSCtMv+yF5cfTR-+b304wg4$W*1rbl zq{*i2#&5kF)s?o*fSy>bywyrnhLX-_JO2gP^A_u}6;SD%A1&|imu4g0(>%JVv-3nt zpE&SxAGjw9iW_d}*eLHU#+4oaXsr)J>aXnl3f%LFI6nB&cwcGVv*%Qm zc>_}rn&ambR&tXpuS*fsOWrbSrfxBT)dE2WibG3ZSMcWj*_Rcd-|$vaAvI&9Q~ zy){zH#kc>wLO*4bdVh-IdP$d4;DZlJg;(-AW6fNWz|m{j_&|HIpzLQgMQ-+v#XO^>XTprj{QCRm1Z&s~E^NZg*{Y0WRm{IWlkBf7U#02K-ChnzuHNqC+ z3!P80I?I-RJyRZUF0}rXlzK;^RXDinff4iL3Nt!Z6t8;ToAH^JaUMsJ77CBkhoWA( zd=Zh6v7S#w2gQm)LAIZhjnFX37B)RlMX@9|FV&1(*$y5FVNvkU_a0*A==PYX>QxYg zpaq4(Dmm5EHB7SYn@7jrjW1rR3F&fS6zRDN5*B-Y19bk-$C>H(ovQB&T)6PH2E!*H zcNR`YYu^zbg?D_!0%f<4MW5f9zslD8r%UJ`1-~Qb=lQel#7{EsK6J$@vf!e$1jzx_ zhUEK5+HRb(VF}yhV^P2xEoA6uD%vcnae`8<%ld;pLr~MTiduELP_AO56{F0eIZ9D+ zOF^)>J87yjn8gYMrZMEBP1zG~PBh2&B-+BR!z65|u|1K9BvBuyM|!-zUsRg)CZ!1$ znWEoVc4jKS3xSIk1K$I;;6zS0WQ{XfV{Qar;|q=F1(BMf&^58?6q>~nUVH^Jf`lZ1 z=Nwox;HE=0T_di?A)PL-+O%9pUN2drY%V0qkdLcvn4MH|gDl?Q05Q{y=&}((?m;p4#{6o|2!KCo2;5XV$%Wy>esyq4OIt zO04+zp46Yr1Xyz*3@FZtE(URDPo5Q7;jAm7IpiXE4hvHLVX9-!A-F)q*NhDR#Ar7GbG_4`gjo8O zAr$(c!ws@5E(;1=ZBX#}GP-lsqB^&`Q>CU^^+N1XtS&ioBS!z#6NA+XJ$sc`6vb`a z7+3VS$ae78k%@Q5aWY(CGLKJ?1DH({}~3ckfobTsnStc6Wbn7zslYOF(_$^@^D8 z|6@c>jPPvf(@6WNE|+>fxx+UG=dRsm69?AY_@Tz@}Q?-fP(av*?&d?gXE6WcIkojr62V2%@y z9!5M^ISE^R1o2hfYFJ0WEplA^?gY(B0c3T2ZL-lN#H;W6JZpAVYCrG3BTmd?SSgbK z&X`9gmlm3j_sCD`P81T%12}@$8$f67YQ{Ou42SM1cU7to&TDWRYCq$CXjTuEK5~f+c!WID;NyIqs$& zhv_*04n2dD$ZiL3(@7Te!5EL*y1jb|W!*SE_3`F`fd??t>LY(wZkDFLD^0FWno6ol zPU!wutAr4gomP;PCa;#KA&QXH`RQY+5lC|^zP}xWXE^ElrFzzijy?S{e5LbFr*0vu!*k1be9j>;zzd!eLO+c73l@^w(X;M( zj6=Giv|kS;r~Xm*$JFHMUxzq4xh=h^Ej^i@^4SBrG+;@3C5!TcYPBSaP^-0DfBnV$ z{tb)HtE-b;H=DPOS&{XV&WGqpl>471cmMJIQrEX=dcy}wA1DS7PSJUAA>4!e5c2+= z*>zm0E313OyeIGUO9W$h#n4@|x4ePg2i~fS=q@)kPKnehY7vY`0Iyx>zKfaQooqfK zO-VSyI?||WD$OB%{ggwRkwYLIKL-Gd_=TPqa30K!I(N(o&AAc}j|m|gu2kn`=n0t~l2!O4^Ow^iW2Ox}X1&aO%;Zhypnm zTRt=NL`hJPW^j<%6GJoQu|_x;1{MjS&`^XXpy)}1f}Ha{y_6TrMT9_9NAa_S2*1qg zM4doFwN*oy_%n>k4L@00t;kf>pIH?+7>Fd^v-S{p!Bq5fxcNIICeY*sv+0EwK&@ou ziO(%rB#ZlT%5Mv#k=Ky3C7QZzhB)>S4B4D~uiPCui0=3ADB$p?2 zVPB-$hHULGJAS3xeqEUp+tD1ZQfa6x>mXvhBM@fUWf|x_8*@kAoURSI-tjj};1ci* z4uVpy$0CvH0iSU($H$d8N*)^AB!7FocO9 zWlr(8j@dG(Dfb=CIqI98Z@MhO(MQ3eI)~>Eo=chsGy}zi&o1Dx9Bm)`_1A)YTOEIh1d7AY@6?p&=9Iu?gM(a7;LQ ze|XH{iA;n!1o#PaJ&PVC)|<|HZcjHLm=LS@%kF1+^e+lfc$qi8;p^KUCQ_vx{QaIS z_Yt0)7VxZh1@?FuFZpROToO)bMz2(dt#RYP(sqZWGBFHu&jrn?^i!Y--|wsKl?8`T zSi7CtuJ|<=lAVh0DeC*P6@h_A>>`W!M}_C?F+v3C6vYFhUHy*UUl>0k;kl86jg5mu zPAKXj+_?GHy)nfDG`+Cxg+Z+!32>W%kUODdZYMngB|r&*^L7!7_65vixQO<;C-?1j zAln=$@RIxh<((3oIU%~+@ROf}Lk^7nr}E)ZNfn!|O1b#T<4rZ8@EY&&|E^&iP1WlQ zgN&Zkq>;qbk{U}0+GSuNJgK-ga53u~Ql-Z{PWtURX!&>~aByQc|7;lfqyShoB;PAX zs0)a?>+RQdyp~TKEE-G*wZN>wmG(-&pw%1xn=* zsUyG%AtZ^ncoE4ZuoX+iB3gv#H%F(7RA>Fx(`|NJ3hWP6FnCco^3RuJ!nzpf*oIY` z>OVgCc z65%p3{$a|9nXObLB#Lo0T}sEL9~w~LlnvP>lt1b$&CP=uv>Hy#WV5&5?t89M9~pL2spBKozZ4h2rR1lM23Rtt4&UH z+px`A0h|sG4K)gan}Bjz4c8-iOL<+LXLjp0&pY($)M`z&yjpm~H-|Haa*AS_pv?!o zFub9~LgZxZsPDFHY5H}v*}TPKGTr63$;-#&cvM#JlTYJLWYSK=8=- zh^%;7C#S;6DE5G?v?1%jp^b&K1*(=hN$GR>YhKe5IS-yzUz8WOKCdY6oPn?6j(a`Z zwqN$>&I>`ySXuQ?sW>Ik*^*z5I-*sK_}>ZyS)N^XU=c6-+Mc4y3Gc9n^YHxs%KLHM&Hs=zStJ&ANoFX&qRi@_PxpxcsobwPN|E-X`j6s`0-X zdRtwPvF_|%{aae4m`h`-8k01ee z29MC9y*J|a(QOhDyx>0@6jFlJCh1>E<#<6soJK}P_h_@Uo3yFZuiCh`In;CS+Ace8 z-(LFMrcGNp8_vOVG_aGT>FlmVt94;|Ww0|w_de!XwaNp%dp)Myd#@|BYT8COk7HSn z9NJg#?Ut_kYTs9DO9RIp+v^B{F@qGUyW~I z9lih~5mB8paPfc2K?gD{hXO(DNhLbobx;z%%97qW7%{n0ow?J%Zxy@iwQk?r6vAn% zYh7K&^DSEjHjit~Wp+NSG~ENINfBYoV+lfrCeJ1t@KjX3^1>Q!-%s1)18!)y=%EFO zJV_?4uCQ6y4PS;ucs`_Dg=utik%JkC(hd;=E6u8EIfiQ#EhB|X3ClgEd3KYI2Cw2Q z=l`U)w&;*xEU#<8yf7mnN%&aNEI4Uq2A=E)`9_WgQ`v1V zcSrFiOh3sYX$rErT?zYRd_q?)H9XU-w-iI8*BX{a12YHp< z1;e^$3>-L@!ViN!-)pU-8=OPyMn~5z-VKsQ9s|C1Od%8QrFvx(VYBWYN%FDuF*y@B zw1r+oi=0ofF=tV^QAiNwm(b)YXhj%Wh^UJYwN2qSJz3p`izNB{Y0bg$O~>N{VBO{ue7jTx)) z`4IK!X)vdbxx|e11T)?$y0&J&=&0RNkddy+ZXg6<+NYAh+9U81bhZFtq-r3m7}~Z% zNtE${y8@kZlI+EPj92GnK(^0FFF}zYyjE_W6FF6L4>ZPBNvMdAPk^EI+jmIQ2QFVe zIyN-0MqT*8KB}LM`I#$94!nHRILYp&D9r9^G0~uaDcosdsRz%}ua1ym;_HNAc?32%o{6 zbvf02efRbCRjUnAZI=7&9-=Ra&)TyQ>N&m6jYE$I5rXEgN zY??6b9=!J9V!(;1DaoiBmWA8SFqt5E_9iO`tjPKZ%d^m*)IZ7LY8Z`q;c1#`dD@r# zkaAspHS8*RHY^*yt@AOFCmnw8$h-QZD5E+`=dd}!GsKmpC90=l(tUkQdGe{Bb2uwQ zaOBFQLgjbz5#wsGmYo6QweZRk) z^or-1`Fs$7eHMOQw^~%U#pJ$dF>v2eDy0zU7;m(m`a4fh=&-mV3`52v)L zlt@Yf<)ShhKH0^q3J)6vZRu!+n;b12+#n*YlTwC4hFWjB>!!BKo*VM!hI~2tY4d~5 zA$oN-^1Dh#lZ}!Y#S1hT;jxWy>rkI$4(2W7Lr4-kCMAms;6q)GlcUFsX}MNp>}jO! zB;%Q$P0@K6Dq?YE6!mPFd+$Vg%k4r<3HOQlXt`1zZD!|K7Vo`Jtm<4S)O?_M2p>)N zs|8}r9!zb#`i=hVgIqzOw0Ez%fi2ko;DeAeIm4OfWvMC61Ql8Juu}K_;K1?5Ddnl5 zp{=czoT2gPsM?fmnp-gDzE!KFvBs6t>C|g3R9B)}kSl zyW^;kJ7j>IG0TbJMpk-mLFi13i{m6hzh+GcNz|B;fQ&d7@6b1v6is7&qr>eQv?z4 ziUQHWrUr>-tx#sFjE}29b0bz%^ja;xc_RekxKU@ygzPDPC&D*bM9@I^(#{-5IPGP4 zWC<9Y07ma}bh_x|uhG>QO&NyMsRki71Mk8({?GvB-;+-P<|`jZ)%zGBhvpw_6nF3;w)n$V!wTf1# zFQGWY=^vJTSc^T=1Bn1>k+~?^-^hMVB(kjA%CNTMU!CJ;RYbDab})w$V;+~xjzjU! zqc!O5ip16J^u4K3{Ym;&D61lxv1Zt|c5@WO4nvul)BxvfH(6@RIzUZOtRwm>MFjP2dmFM{0P!{bu{@t%E}7Ux7FsA*+uKk&|i=>AtFtlmiSYmnhD**-&Ofs ziG1(9p6%OaJyc!xa|IJic*0t(kIW?cHo1)YY1d+6cwI8{+g(gyOTYM%hh9Xc zk|@;?d6kuUNsrvwvhqn~stF$v%)0SJO!j#tl^=46wOaW3VtIa-y_SfW24&f9*AbBq z4s%2##|T7`oHHj=qi|T{=H0}1sRdC2W+wKj!lz=H0ZB8ZImL?^Iw0moSH5B*8CxX) zSN+4ZZqvlXCfu}hMq_!RsvFk;I8~a7Tvp z-G#|=zMEGQp4ba`T-$%hjpdUthVYkoy0$Tbq$Eln0sw3OkvZJLF)T5|&oZ|eE3gyf zK?O+@BpCuXb0*0SspF}7`gh+EYnqx6rX*^An@P;WvG0^8NJj8GtXGW@0vIc-L&OnA zpnW&NB=;V;{$#ZgzC24CdADxv-SB>C**rrlyIX$9;-lxjj~a%r7m2t20GeDx?AUg) zIhNVEeuwC?yUsOl39)uuwwLb*tf-g>b}<4$1aVo=%^@u4Y*QE@N6~qRoF*sRAlKK< zPr=wD?cU$>j-mRD=42XARk zrH{#}vGklu*`TaIHXwUhM#5BH229`#C~vu+6;9yCtwd5p-X6nlX@L=401%TIdXgMs zE_}jxnMq&_%s@5QXzZyn=H3L~+gJrrHxmM@jEks5U7mVFNXuw=La|SBRyOY${#>B| zP>9V|&ULU@9>x*E88ZbPM1U4DjZ~M;n}v5F+qzQ?=!@^2_ph z`F}5-??fF9--3qri135Lsq2xh_9_vZ7HR$y9ggS9mB?D*I6`4l4SWPOBAgmVawZ!L ziC9EG7D(D~nKIAU9p1|^M!>fz;vNY%2o602j0 zQK%75zEri&FOB7I^3?{{IuSp2B+e>Tkx@!fk)KY?7Ht1`83}88 z;@{zgkS(e&d`I!+lf60gmD4R%Z}7#dKh;6Y<~k%s&+PSu1qnA(j|vUDvJS_W*WJ-Y zT2>$J4tWD7%YQF}`qdSE-Wm5=S0ixk)tQJa&7$X+JDN4_$s5Y)54Q}9(_Tw?CE$iz z`hze0;#Nz)zP116r>Nov;I^{HY796>cNG5@zq)tGB}DuIA-0E8+BKh+$F}UekJx3- zr$)9ffR)bZ`&{mec22%yw9BihaD-ZJZrWxh-Rt8BA$AX7K@DtVDm=HE$jR%kOO6X~ zh172gi875!D-4f0E2e~9=NC1MRWuyk)0n7=f7$-wx}(=Bs#SNAE}dN!t5LTeG3Ce0 zah8D>I;Rye`oG<%v?Cns=3VBwA*dc{blw>?YTj8r`g;U8e(8Mf`jV-fI_dLaE3#rX zICMbLObp&Bjk4eeB`SHwHK(R>4hpG>LrO)&Z@5m^%X);hj*oE|6NSVws&LZ-0I~kjScIHagS&8wSj@xUiWxvD=LVb01F`%taBEDwqjxsgK9BIr?K^NJ{96^0Y0$4u{mX02 zC!ee+nX+ie%~f0k)L<)pi4p&sxEcvd>`ElTLKQ8)>iha4C}&?= z0vlmtNo?9|$$3)bRxG9fCM>!_Bm6%DfZY<@aB-7JA~}mWl7;KpKrlhV&mnb`Cd#?d zM2bZvUjdFr|T?`E;-fmG`xq=;xd2zH}|ro<6ZL_F@mQjky!)mmc-$Rgmz zZ6OPXr3k3)AsY}U>1RkpoExgwn%+PHV(%fJT0VMPi{s;LtCKx>mbeQpao7Q6FMxbh zF8Q*7H6?HMlM$*U^vsshLnPV~17~a=!YoY(!FMkkih!m6`b)S~?wW#kta=Ke}O_PI@gVWI593w3_c|tg2WwkQC_~rkk zX_clzvLX^65j|EplkMGAe}czKfu-)GyA+?^*qwRetp~81@+s4y?5$Y8Z3r1>=}3FduMHhu0-ECa}FkGcLJ6 zm)9!_qRd`Sc(<_wbIOsf$+!jpRNm93Sre}nIvyhM)}rd`=|O`}W8R%>l}yw)Ws{b$5uVD^`rghA4EoGU3Y&WM`6G`#R$J$@1n##SIyCfiW2i4H zN)x5^b%#skJzW<6LtXnfHv{P|sFpCd* z#tq{Tq4rCyR#9?^6X_wY3ZE{U0f-pQJZj(u``c zzkuTgWrJPoNf?gT%5fZ#llAL8xA|L1IBYt*E@d$a-VLI2>y}>LwH7*qL+AA<@VEtI(c=#@gk40)BYj-7FRnm zCQ5ROT{m}ma&EG(+%9SsZvTCBLD``4OY~)JZ3p?bvQ_m2M%g5}QVyj(c0XmXl|{r# z^Z2;^d75=jEy2HkJ8o&D7H6)*dIrrmpBNoJM#PZhraV-JcO){}G&&N}?bf{G7Uuc) zRePl&ZbN%Z+~1nxI+kcy0)LBZc~P*V*qrNuiLq1q#WdaT3>kfu;0c*tAEVSll^>

eaZ7l&e{1638CUHL-V5Hmb0#QAt=Zr$)`}HCFeMD$>;7G(Zh2p3Df3e0k~-wk4d9YO?sU)}Eh!lsz085o*#m z{JfG@aBvQM-ed=g#G>btR@YwMVG%80r?{-nRF_=G5r!jX*a?Ot-_VPwH~p`_)(%_S zmxTaR&T~n5eC^q5EbP)ze&dG0!ISGFH-+-G>MDIzR?^WSGSL3(qVyBnQDhfH-5s;|E+UlVK&*NEXnMI}b~5!k)0^rsj0cXr3FejGij z=bQJI|GTF&G5)WxBK;3%lONS(?CLXpr@@M+-=$DXjxseteN+*VnW=m*BMfE#3nj~Z zu-2<00i6$uHzt)W5d2`$lvth6bp3xfXWOJWnf85Z16CAXy{fA%FxFuREMs2n7PfK>x~tDlG#_hsov0hSyt>FZKI^B_$JsCX_cMXVR$iJV9Nfz zVll_qRrLJ^Mo52g$67_%jk<=GqjEuj?i>Rm0X@zD;YHeUAB1|$4U}tNBAoF)_0XJT zHXKsFlW40>cyqU$1x_+8F<$q)%hb|LlMJ&vV z>n!FDII+=K(HLa{q2lZx$TD$(LJxqcO+7tRy6_AQpgeF(*6kl?K&3rk{VE)cj5t{0&fqv2V)ug_$4@L1CNBV|g9BXie@r!vh$JmtXx zU(eos$biI+d*w)HB*M~R;1hw>k|j19xF$31&|q%ARlEx!&nMJgZE6Zg-}vrxZ@@4w z05wfaN}fti5*6*Ya`{2`q&3)%0l(2IvhOu|Flm3+OVUbjz!g(gj~0)thK6S%`4rmdFJr70jsuTmgP1yCrD~@cn+pImEDyc`pOOGXZhwZ6sS-k$fV}=Z?j{Et?A1( zm*DW0PfE6KNDlF%2x zmRw}yv$F@7*qh@%9X8Cv2ZxnOUhZOYm-N17AHl9aYfjZ8+FcI z(3bj?8!K=pwPrG`co~VAC3?D@%vL~)RNc#8SV-xjyc^DX0}IYNV%iiszEpNH1ukJZ z0f>m8oy-VUtoXfOkjV4%eekNwS&?Oj`ktxoJM-t&)%7@%M^x`kvybfQ+;b#5F6V%C zz4gGVves4FQn;!Hiy~MYZpA{ zoGcLlYt2=Tp4hqGWp~-v-@oJZBm2>7H3T=L`|g(-n=H+SJqehKJ#S3&tKO*Z=&;}a z#B)zSed8&G_wcQE6za0sSgCe%&Dv8irVC(q5#xJ zV?h{Jw-aVr2GKK@pZWy@%Sb%WOAN~*SOB3sbtu`oR7jMuzkdFg!5%AxN)a7V4apW| zLnYY zCLJa_cV!K0M51WTDE7u(op#KoKD$WD55++%r6;W8SYgvuk&Kr~Po5uuo}{spwTQM? zuPF_?4Exc|6BiQBX#9iFKbS!77GC#O{bP7LGBoX=$P@Cflx11M&h_{!DnQjvlf&KT zSeFocQLUN-hN~4TOGfvOr>9ZXa5T)FYS`Im+PS==$;>r&EboNf$^VuPYf^()cVrUQ zM7e~b79(s2Z$!8ukPio0I3vAv`buzwM>t+|7>>D5?4F_7^jU|)2VJgNxj$P(J_|=% z>f|x1iZ@8I&CkG!L<9i;5y=HU2mc6oe(9o^6e5qSpht2y1$d$K*b4Vy)%W$Bi}QWT zb8TKlMI(6pf0^$8UsvEGtV1(Kv(k^;h1Gcv zDUH^6<{s|}18ZJD{F-Xfh9ClnY9&dA4MRVEy?(0t0B!B8>AEUMJS_F-Po5kdAtQrJ z3H09gS?`^GBw6^Ew@y{Yx0PYtb)BtT$t#z%S@(~F5)p}kqeUzdaZ#kP!pbYG3JLRA z#~Aa2PajVo;|b_)Saixb6d}b%V5C|2rtz5E=}IZR7|*7ZTucbN3QI^!xLA_H#$PN| z@Pe6Ph?)FmiHE-pvM24|qDXyqdmZZEo)hpGlK!^*iUHRwd^mR;i|^LHr|+KQ?5LgtG#@>VXGV(V&r0UWt9C@R?Xotvh}E zZ+TlP=1CJp_@ZfH(Jefe51O91ebC=N07_wtkffK2ELI0{Xs%In`Z?;F2F=T1MG)fb z*v6f~S4}ziQ|bAe&Ip~CP{C}&UYn5cSs+zHmI%Je30#jc#o4SWKtIc$e+Lk3OTdI+|aTvGS{$J51s&jkPJ-Of; zot7?SGuYRvpBZv3L;L-TUGW|^awc%@Tp=KXBPi=LGBZ&6USFZVbiMhjOSX-1DLORs zwJ$lvwe`983?jAkwuxeESzBA#>bjP;ip17TX#?;hvee_xs`7BJqI4asc(0?wV>WH}#%{^n-o8Ea zA1y%juHMZVu-`8|VBZYI%p%snmFJb0Z-@zvp#b|fT`qES_$^F|XtT`ozSwMSLv6OS zjY+9d_@iP@W2QI}JxjSnSuE}C2M=_#S*)`UzEbU~TthVg@ld%^;w6K?GdvP_qA;86 zof~ltf+PgsXzFw`q(g8m{zPar<4XszC~#!dBU#ZCehI*C(P{;a9FhEI0*}Oa2KQK| zLVVVLI}3R}MVO0cOTBOYCME>uudVbawe7zQNQi(Bi$m5xkHfWM5kr%szI1)py-{cqg3C)vDqqpmsnq18 z(MSq8_L8pXKIy6{335(IsZN4{OV?Pcv|FBB_38ng;hmnht~Xy)fNkl$H8LW5jE+F^ zp3%1!nQ#Dt?gI{w;oFei`_^G0$h15Je#_y>8wNrDrC)wU9PURFxkG;0`KO!wlX`2PtHFLmv|Q(-4oGm`&oY8%pk@*KTia`-Ur7=rxQfVcxOdEc{p6J(-TtdtcHu< zM3F=TvLP#+ohmGGO{3TQHA8wXWb$M74Fv3KbhQxPn{H7p_rT#%lH$DUuFA^6`OTZB z3crd53kqBZ{~87t(IoO&rmm;3<3$X!SysKHCP=n|*;!0WH}S3YZEu%;j6y}JpNh!=MXGTVH`+lcHF*?gT%s?B)Q~NbqUqDA%m&(-4QuDM7)4?2odGn zCN_`E=WRt+B7{dM`29h$p-MIiE2kI5WB!kx)v_#a8XxaPjXQTXI?`Q}()D$5!XqdT zV$O5Xq-Yj+E{Xe*aStyl!pXbsNU|`p|Hy^Mt(1*EIo}1Egvw7EdMzbwL4e~1!O^JSw5WZXZ|2} zGl?cAO%jupQqq-?R`nH1BqU-9i!&BPi*spmSarHXPARr`$f@%0U)F7iUhA=yKiD7m zq$gD)^{fo|0kcuJ_d@yRlmt`3bBK$J%t>`XpRV}tFiAt zKX$x7-0Qfa?z(OcL7t)%lTxF49Md2h&W~z^@fNR!0kf@-VAA&)ly&4}#`LfXPE7ls zy4Ic&nFBq;{%&8B;Uv?bPdem>MvxBt3*pD{BfX1$G-3U6i^BJEcrx>2qP>Q+?-rHC zm&splDario^T-mAZeEjVtCA=W#Lmb1#rm%Kk4djAyQ|Ukx?&~px8<}3w%jr4>V7)d z(iv((abG7N=eBD@aCTzne29KtVEE-y_*_+$m$RZbyhZ`6opbu!O$W@AM_0%Mf$0Yqhs@C*0|4ghz*QH6)KCn}9~Hg97Ln0&qc zhppqii>*tq`9^031KQ@RuyIkE&RG{#J2R3sFBc+k`f|VQJu&2Qc|_gzU(~@%q#Uey zQVV{WKvSovKYa1m?Dt6UOPU(p75q>s!FFj1F!~fbIa%Rm30o@|p|-p85t0lLTvwQ# zWO;OxZ<@7ClW9iPf9cGbOHNYBi}e{K(N#YWlGCQCss5Xa>{GEzCd9x)x{plZpi1IVP?4YkvARQ&CmzEto`aXyVbT zd_EPbb)G&WO`F!q+a~ibjj*Nt3l_=Z{?0WyneZ7kAHuEg_AKmX?;M^1pVqFOTA#{49To4@pzfUVghQbgdpuP8um5NlJ!T z#nzgF?QzTRNe@Q^!FT2iM@p-Tjil~zPmQ5-uzFo-C6rGZwd%Z2boxJwUR_)Y85}g083#?TQBzcewXDnKSf*W_c{stS)}6liPPpFG+$$r!T7N2OHV6^B`@ zIZ-pl@MQR5h7lbOKkACTG`H~}a9D-Ck=0sAX5I!CW<+I7Q^-BkL0DO~&HJw>(~kz`^|Sw{(qJU*MoLLdkiV*36RP z+D`fR6t6Om>aQ)@XhLWkK{T<6{lsFp=YJuo#a0;F43u?VU07oF+K_7IhT;0|w~OT( z5FmLwWa!w5jvo@yMYztHlD|3(nBO?746ja1S9U!crBuI)wPVF_CF;iAnX2N&V%5?d zuM7$T@5bbzoJ`E)%=bH-h8%B!BL<5VN8GaRudQPnG$xLjT2it+t6Zx6NJtbn6fd+B zb!1#%T;%pIGp907@k&rKtDB+G*PY#d@P6pbikZYXYw3rT`eO1g*kGT4!>+;z8wvt+ z%2=ym^04TEfl7HfbRt#5vf#va*OtXk+U?D3|71`Po1sC=1|4gQq}ir z6nn}*>zQIFnCU0dHGh$%`=RwFrr?4rvy202Gt24f9O@Qn5mcDs49y#9?uFtSM>%9h z$g~2L_&!PC=KwtP%gof#+S73zsZZm%?N=XuEB-lpo_<jyHVrV(|;3(U4xX$%w7^r@s9+6K%zT*z^Y&DyLnaV`f4#jU+8k%5Axn6kgX=c{3sYleol) zTZE({m5h-k=PMK9k|^*0sFHt*OQ>1o6$C$qI2wyc#9DcJ&2s9u1kGta5j(J!V<%ay zg>|sYzNiQe?}nRd-*JW2l3OyFjvd<;vXnHpN)pC^n1)6$sB6z1{mecMMzbWtmqU%F z6*_0i<8?=aiys=;{3VvZ;V^85s@D|Prq$agO=H`P&32a}b=6oK>1=`~zgYN;vNoRt zx_RfjHIwI3ZsmN`{n1BKdL_-Y@TUT^IX=!PdAHZNQ_YeS>oQ=%SG(t5%AO;BcU$7# zmsRXNV>A9feB&wo~J|wJ!hlErF8wDf|=r{BxnH>FQOB zCYi_ArcJ-0%h%P_+!nRzku`k-q%6k~&J=UmG4Jz53e_+5b@lHMs4Nh-f8f|dS<$_q zbU~jUL3)mM?M8;iViL1(qgg$93=EyyCfI(-n%+&l=68HtYi#Ky0$=&S3nDMVOe%s6 zPSFW_cDO}x?Up1049}Zv;;h_z1~35Vv;$IY4(ZF)s# z4{+;8$g3gI78&;e`d~Btdo-Na(DeNCO(#Y7 zW%26f-sAjo{Rpqt;_#E5e0BLts%9))VoradUER1&6BNlUIb*_Fm^;)>raPj$s{y_2 zX5;@&ZTRH8ipu=r_3MlCL+g1f3R>Scu7#pKzda6WVAM0}1m|$purt9Jn1-&u{zeLG zn5Hk8|H%J;_nXUY;`_~b#mqUI2We=?N#fqBZ*ERhk*7k#F*{${u<_~Wod&FRZ-PND z4sP~t9z2-qm+%=iD~iOMo&M5pZ2bKb=^ypt;nup6Mbf$fR+tQ^?_EyP#4ccr$cozgqa?Ma;W+$bZStto! zU8B3x+_cKgVFSe5Yp0Jr7VDuCEm+n@KZ!mrBdVAsRXTBBYK28ODvt00GA+&0ZyKjA zD5yJ4x?cl})-0Ufx#O&>-I%&gJ$QX~ZCM+Cu#I4;E=!2KrG;&R2v#S%;Ns@2(<<_y z8IChc<{1~qbps0mRNp$qhZ_Nk{l04e^Z)5M*X6h>OL|fZBwO;)r;$V?HjB=4lG4uQ zpIINJE3Iabgk407EXN7bCB~++@jT{5oyb$J+)M4N@8}@QuBAFbux z_3G7@tE=6YWMf<3k>1%lR`5`xwns|uCDN;-U+E}bTt6ne^lV(|l;CjS`fJw&&f{Xy zZgU;t8<#mzGk(2Jrqw+;;??|Z+ruMRguSZcoL6RcYNv2@EP_v2eks!3W&ZYzdCvE0 zVB);K16~QH`}B8qJ{tk8atrbH359`2bP`9An1{~|(;}9nwZBc&afB6-$S!3;CwOxX zxRE8w%c?v34d_#bus;Dl9o zhaM^S63pikA%3)V9ESXa$?QL^tzH=#>`B8bH~ml9lbZw?i5M3lYP_zC6F!N|j_GoR(GRqsdi*gE1`(l&HO7`tUu-FvWHrnC zl!*-#`I9!?{wi^sso1cgB2?~|RtbLE#VF+#+)b_LNrMo@0h_uT9lc>KNcqzfmWpOT zdI+7mk3tL@<{Y9=(#M+AAVEPZEnGU66pHcDPTv||@odY_IW zz40*Pfyy=QP3JQ(HGKuS=;>8eCf|w4+e>v*ZM_@05p}bSl*@k;-=B;(s_Mt4 zZu=3C23Il?#cfx!4E+B6xSg!4+eBhPJlew-ME68?V@R@muY4JpPvn)xtJu_%=byLv zqGRchwda|Tx3XfEFt^!m5xOBlc0xB+Su8$xq7-Q=EN*HXYHAv4Y$~P;KP0d(Up9O5 zr~XpdmFQYjS9I98?z_?WP(D<0bVa1`a1pAj?Y|P76g=VH!~$8plVTsGY?uKIAaDa2!RZC7Vk*S>u~hOW^q1|PGy1&xA$80HR!U@0lK`+z{)rq%yrh9$dS{bCM}UV;*K)lA6j#GR)JPp~M8n6OY0X+zI~EPQX6q|u>d zpOzz;f}}_-q1`sFjG*tINAn9x6k!o!P&<`>Byr~eGOu~gHdGT_Ibax)- zjyrZ7NM*}d*jdMdc0*=z25SD+GvxpVx^{};w64XC-0nkSyY^KWJ!dqra%lO`!CBAz zRh2j9P0!+wk+mko03$Lm4%ii$n%O1KZ`N1lqT{SMaG7V4NV3HrC(s$|8BSrvJf0Tm z!ex*a@r5IpYZ&5&3^q)#WLuWdAor22;no~R48l!98ZvkjnzB&KiW8z?!}bk>Vb|e@ z)K660r)Jdm>EHGX%wN_A*B@_>6m|f%#{hO>$>1$kr;U>zztGMeCwJBFAMTl-Pul8f zSU34ZzOwJG1 zEulpOAu(^`oLq-KuCF|%nPO^GDRY-|m9p*yyNx}+ghEVikZwGzjtsvDnxwK*w>jFx`K5~B|~yAh36)}G2H(JmB!p~ z(ekL{BI5~LU|2@$@!%a{LvUh1T<*@h6^-1t!Y957g!6WhTo&bU$CvQ1v)QtJ9O_B~ ze_BNRUR{LzqEj~!nMG@El!&`PU7e?+{c&qM{bJ)i!R(7ADXQSs)MFk9AYK_9?JTEG zecM(iCsiAvD_3dOw|D!nFd*P=U%f5Ds@!VvY_%R3!Zok|W#er;i4dB^hBMhs7BC#a z@ZlWyC=YHej=Sd`s5Vi76TrEOM-<_RI6Nf#wvXew@}SLfnBPhWK7LajB>>-mHrfWoMxp_t{s`+35dIX~67Cv%#-D5q z?;$fNb`!2m2n;#1CkHvT@KA|X!V|La#O?{tFINh4y4l6%!SDX)e|V&lC<9OHU5wLtb#Vva{7jkz$4yR3`}Rr3 zO3!2Yjng%l@ixsmKPpnJ@NCa%yz3>jKTMFRX#boM4~e+i4HWZnL?IuQ-+c5~IV~gW z4ARY4_#n&%*gDx?hi5fl5nIuc5nJ3#A|UJlbk*4=x%kRsEf_FbEVr#z8Tp?Lo%Qt{ zBeB|;si}=?=Jacto7WUK$%d925#`@6Bn5(U(NvnAlBB8W>c)0&yIJYEA*e#aiU zOOL&cwng#M#1%3$rk|kG;gF88S{zcTd(@KlOeV!#IHArqz_dto;4xTOYqc2IID;he zB74%jE!}&~%mG!x_8;;ri7y^2cI`}l^Bv97t}B}>Ch;WfzPjV>H;dHG^v>i2TW;$Q zXDo?rr;eQ*-5NO!Zs!yuPq*u(wLtSJbMcPGyc$t4C+2#YEN;=7*PJ zRzSBdyIs=1y*ZlxeKN)4amK&*mHzl|oPjp?P=9shN*wia^`B2^R@YISe%(l(rml;} zL1O>^y?l*%cQu6h_T+=7E!x2SXI~mKt>5+fv(@<(4k3*7FZOcJa^I0!Jydia-8&nX z44#kL(J`ByJv*KNSz}7SyI~}D8{;0!PRir+w){sDFK69<8+GP|(ghNf{2T}oYsDw{ zm$#}~TU5Qas=U#~$w%cjWD}uS<{Wh*j2)VlaSP=xVbaE`jpuS&NKPCc*qNmWj7g7> zk9MO4oUG>B*V8%$>B7 zDCrRWwfKkE`ynF9>qYr!b$lH$=WfHuYNMv8&)`-4J~eDVh8zN6Uv~wDo9L#N5e=bS z1*%;qis-Xd;Xn*sZzKe^>Su?V9c+mE<}HoEWlKA1YkszB42jzu;=XiOTm#wKK;p^) zQ0!74*w>!^@Ki|s7TdaD^(EKEQwzJX`&Y2EBKQIN1Hq@#*fJPekT4|?HaVtORL}Tc z$`U~~^b;cyT;Qj+UBpGi!HA5IM~*^2*4;J%r&a{c(bk?{!FcmAM)uvTP~64(2SM{5 z+ft|1Z_avdssV2##1Bw}OLH9?I@Xi_*i+RmV609m)^Ubwl9Q^8Y6%P9Jd?J@k4%S5Q!?@bnj#3C0No7wSe$>8?)DncyM_$Y^t7r= z@6r#C0!Tb-Wfe^-0uMR(p2q3)vGmdOz3H$cu4SE`)IZ$qO8=L?ui!7)f4MjVL2;Ag zL3C*Wtk|j~%K2)YS6~sw84Yb^(Rj*Q@zADjJZSIpsV66UqF5*P%CGr4_{CmT98eaR z&3U==_uQjyvZ@f3x`TMwRObU4su?_fA+)MW2NmvVLG)mAxMxq#9SS zV0EHDPc2iMO`}gDWVp^c-BM?fbwT%lb9O&G-|mt8M+!d6U!tV7xN&6k?C8j&ql4?z z74D1%ABLWrBf8h@P>U7V!01#{l$M(I@U7O<(v06gIL521;EsQFtx3w&Xex<{zDjwe zUwef*l6(?^pIESoG?~hJYgUuc-`HsK(#eMas+nG5NIezK=83ws8IcalJ z^5&#Oc>dp7-Su`4@56WMbB)fIF-JGdrCQUDFORrfs@hff9kf--A0y6BNb_%?FNc|= zFV57o{`lqeZrC7!lFr+HOIQ4lHbN+2bFAu8*{o_scgE!kvpddlDH08*0<#6%jWgJ( zJ#;9`Wx(ux%>JN(0cqd9IgYbRP%b8ipI^M}nP*NNVnML5;B~?IN86G*n>3LiB{u}4 zDcJS-lam@0ACwKBK~a#WRkBz;X7l0?H6geq_rlU*{hJ#)I;!56U+%C+`e)BS|Jl+0 z#pq~^Xd_-$AUrc;euT{*)*iKdXsZ8J&B8Ux7k%$ccR`&_2PiidY-_XG;!fE!`IXm> zyJwPj#6M(DYlt)1!Y~FlQNu6TN&-To?o>ZEEuGh^a$A4X3hS@YOS&>^ znU3xwobNk5moML%z3kCk8qL}Kz{bCOStj#0x8H@p)0h2bJW+p48{NDz{?%~%8)x%c z--cUDn)w4R%F~69SkGG?S%&pO9!?*P2)}GCc~AG--POr$!@+ezQ9^~m(oSXYvAAME zFqn8kTZ3#$w${oO*jV^VNt6u6a}JyB?86Mh?IkA$AZJS*i{FFElCDkX(|(bKLgb?| zUN$P%@v^;&RnqJ_G4>@EJ=F?AvTeK%E%VC)QcC7|Uf@^B*^=`lxtItU!YJ4qLp6NC zWaZ5+)LE@|=+7ATy$>uC3vnLtnvlL36uTvErsBHR_q_go$*a%?VZMp*)*{Y5|I^S@ zU0wC@X;fPLTcZnG+S>!S?GO@z7)C@`xE;+bOZ+0!{G^PF(o+zvKa?AXOc-N*;iT1K zuG?_5KAnwlwpb7m29Bo*Cym;^rY0KiS4P6Hyv+p3o+E?f>+*4^zc?8(pb$&3{*t}ea298? zBg#2yb&tZwE|zje&8ne1sx47iJ$!JH$B(6mY~Qon)dT8bhnh%0q?tq<6iGdsXcMmz z!>U=j=e!BsrRO;{%-vYE2-~M-Hxjc%j;FW;>+7YCNkjV^U4G>PZn2KxpIa^}rUm`glRD{tYUe zr1YT|Bh2d6N!?WfX$m^yyMLrz*MjH3==>EHshgGFu4_O{G+IqG>8sh8EVRt14~S{b zjuB1NtIkHXai#cy4gK^>^an7^RP*_$-63I zB>kC_=+Vb&m2TAotex~lL?y<%-|ocH}cas%iUOnXd#zQ z6T^F!KUcyX&2#)Mt3Y;^FE=>!4$5h=cuSOX^g*8RMj6BS`Q=1PpZ1LilX`V>_iZ2D z=iF0+iJ8%bg0H)9F8uoKbm&yCo=%9E@6cdZ&yuoP+kDKqTUA`cBA%qT-)h2y!9@Hp zsNLZfB4#MvPkHnBN!-Lxb9>S2$$LSx4i-z8bvVQ45_dt=&k2N9Y=xD0kk1KO<8nS9 zqng`*-C*VyN|0p74t!ZZ-FwF`F&L1+2Vn71kt~yqkyjvG7ZcY%idhy|kB00DOV%~L zhI^hMLe5Ub5DW*RgolPftzoz!*3y1UXLX2LP?_)4w>oso42G=P^ncI815@w*5n4^C zeRpiziMPhb|7moQ{(MR*>v4V|*1 zo_ye)6Y&|dVTL1^`6;@_x2@IJk(xwQodMu)o~;SEX413PH#Zfh>Tc;&s(F=WEz7jh&G&in(MDT9dfEeDbj>nK?D2+Z<{u9z4V_lMWBc%^@BbB*{e81KDNmp!7Rt7N6j6`ye~sLt+Ht$z?1^S# z??nQRh26*Lju~(>)IHjedIJnoys`-5 zBZ^iVyn9pMW5ipKx*rb)k?qU>vxD3xbq2>1_fPpS+fy}Z{_i=`TTaM~ZNTI7yJ1v6 z1}$+ll6f7Z5d=G`{62#a3pnPuEB8$i4|z8*^5~<}S|7p)UQSlgx{Gv(9a|+U>Fnh! zq{D_3%;cKr?p0bU|LDU<^AWWwx_dP+5<<{ccMHCkMYx&4+5p0etl_Z~Y_w_2b8EU& zS?~#pKoN#_-ZIO6JA7q0x|`GEy}?0)BtTWI=&*s2qKGjji#FsGotSky34*AeTUu&m z-pDVvTBK=!YVkaE$(U#Hx^(Pb&(>|To@F-SWB^7^?992?NUYjM*YDu^;AiNw&EPFY znA1|q_DW5CFk}$TEL=uG6v(0J$Eq0~;X>S)If;QZm(u=6G2Ul5R{Q>Vq^IeS$34zA z{SCqsmwIC`*B1yhrmYRHu7pJWmb*lt=CHxbr`Y;%bb1fQ{3%1#8HW7RL>4k2Pf;R9Q&0yp=6 ziRL3;QSA5|uDL{GqL|M=_~52IHkd}|l7sGnw57HN%AKV?OIX|94s;MX>@wUNVIKtZZf&w0 zjoSKrg`@JFx-GGvEEeCP+1SwFaVHvie&g7L=TD{{i^a2L^Z3Y!r@dYJ`&!1UI|m!r zy!vW)_fUb0h-;NkOSQGDD`HAogCrFU5C8t&(T0Bc4fAYJ?c72Sp@VI%*#7whY!K-u z7&RG2sRyZGWrUH~|HACJUvMv{O@~8%p`WdS;OCvhVpfFeD(5YK$(eH_&yVgs zKYR93cWkAy|by-16D8`PY<#cdumn`j+9us(HXt(dgHn6$*0$bEz$+x^(#e@AO zQ>ET>tauaKYI-nHr^{x zm%UPd(`IwnDYBBjBTWXiU3;c&SDBUrBZ8#_H0D%6ShT#K4Fmah#vfif84C^mWSvJ>S`!#A*8y z&hqWPAgB74&R=JFqpYoMe|!7df_0@IPM14e$b}q*?^XGJ`;9>I3~e{d4y9B1?X_Yr zHQE*WgNb0)Zomrb92r|KX6@|(^NeZcG0qNsQaAx^2;nTC4d(`e0M3R+Ioff?%LJF7 zpvTeia3=gXR_@Gfk{b-Ebh@-w3loCqSQs4$kuvD}2}`8|!TE2=_fJeFKqze0E<_ns zF{(nj_%$qM$pWR(m|trwtZG)QtfuMJHx$=0S&YxgL&RcGN^C(=VrxNSN-`ZQaZ!+LM0YDxA2}GdjEgKdmOZh%j_6|Bvy-~r z^oHzR(e&s|Yk>*h#N6_e3BAZJ0;ppS%P46MLBTtDN!pMogW4~2M&+!NEK?a)=nHyG z-zRUnV6W-I(!o7zqk_4>LS;7QxHI(w$14(p`L>03yAR$rq8Rd*%e z0B%V_S0c^$2R~o1JwK=`0?&)U2L0aG9(aCe zx3HX08K2p=`^EL^-_bD1E2LLn%ZiNML!XP#C=bVI#x)-<;f9)4$3`B0e0)63ugOo2 z9-*z0r*Dh84=eYr4;LlGM2o~kisFJN5bo<$#I;uCRJF#-p=;NT-JVv?Co`{{c&lVx zKGu13tCR!T6~ny(>>n5#fYKkh!_&lX4~PjwD{USf9nn;;xbCqgvECf7?;PLYpbuWc zt)AP@kKNd~0+sxE4?%1X^9_R^J$MMl9!lwW@=1@Y_|#WCi=2q%tuANK0TJD&T6`B? za;FeNF3u*}Y*1WmwK>I2lKir}^>LB%%gk;|jc+8l*2${LI1?SO!y=+!lD#z z)-LwBBE#;#V;VV17}1d}4}yAsBeR=oQQx+&jPFl^=dXvb>WhfWUD`}o|7?|wd_~VbbICY1dT0xk9*g_ipvf?9{E<(NT{(!lv)for`JTytzH*XaAv} z$MI(qkFD zuKN8*UJdlz%(4LqntDN~*KG06_^B^)drixf@AenTmxRLxGRHkryE9iCjlESs_5EiI z{s9R0&r;S$?vy3PX;ao_uAt~dpy zaK(SV*KIjEktf#TALKJUlw+!-qapLA(+1!1eE0&KSvdO15&sjAw(A}Qhe+)TqTbed z8Tt9fe%O7~>)RUYLXx(mWUeXYnTED)Iu@z>lk0i5*ZAY;*5B1XH1^{c?;)%9ujQF2 z+X{U`MYkE1o;Db)GCSW#$>%z0^Pe|UT%=89qz}~^rTzz;Iap_o$n}o1Nr?gR5j`Jg z{1)5MajUaq@P(GL;0Vb8WfD8?g*MOsl#5W&v8wy54;9_xmhF_PK21`uyXOa^VY~5X zgXGr&A|j4g5t<_(Bx^r9oE-lV95?~a$(H@qOP&|;_Geahc6b`;WV)$>%$ zQC{o6LUJ>=GzUOLe?N%hhs*O@;O+La_NN8;dBoP?+Kul}U=l33>2>o&Pidh@yzFfwMIX^+Y@97Q1Roa@OVWWi!Gkm*Rp_Rs;R>RY-SBr#$Fg zq@+=42wAs{w|@~+mivuHq$|u7haWovJ$KBHULN`P)wy@~R#x^Hx>m7a!x3XeX~(+# z&N^U$C=7i5@2D*o{<2b9iOwvOLQM0`5VMI1abiLiK@tk&1oJQlF1}$zRu(%StMJY7 z7f(JPGK~$fmEr;-lB8E6f;=U-=%NIJA$_!@nNTM(ZcMg*XCqb~@dqwb+r&F@=gH(J zYvyP~&n+}Y9(`M2oO{12C%x&;^tQhe)M+ ziX};c==hS^=koj2_gogk&`r4d)hv)>ZEPE)tr%OmmpfJEV7@>ns?s~ z*-Kq5G1;rWoewEkr3wE`H?i!Ys8w-UenF7$=mFyY!VvnJi_q$u;ON?~{5x~aov+`* zL>tto%m9A69+DvPrC#-P-#L<$Zy1V;hu3 zD+D{$fo#d2(VzWs2`CxNDg>R0Y>Efw1N--XW4Zw|PBic3rv`2hBE~x~K6uAB3_+f) z6Y6>N{A{rk>S@&hr#U5^CqI4a(~pf>w`k|bFR6D|@ke=EekDi!MG=W;JKQU%xcJ?T z|9yS;gDa;RQV*#x-x48p{?NvE2TBJ{C;oD8%#N*Sc>7;tOx0IT<~3w}_^S82*MBB{ zld+DRkTPmCQ^%iF%7+q8ge!lomMhO;K64?d zB7_iBXKq*4#@ALw%WmEJ>FNnjFuj^ZVSo#SCz3DiqP_4dx@GSxr}A<)tT(m_SC&R<3;o-DJbtW{oFW!MJy*cMdw9v*hIfwjhO;Ck(%?0uI`SQ2~I%%FjC^< zw*4tG@sFYS^$EsKzl!EfQsp7@t8OPCTiLTzuN`%`VcX}9&$n<`a1rqI$svHs36q2) z_7*|=;DUVtV(=wvpx9KID{kC)G(*Zbx-=|iofa)u8c!?N*=QTG>Qkx^ROQJ~HfNC8 zObt^0ag2X`JwKAKX5!Rxk~$iJ%cBN&A0Lb#ygE2LA^vi#%{DMPdVjpj{QM|0`a9O5 zb-7?kH#i{O5FtA;sFv+KqHCZlxnTN~nd38IuhFBEqSi=1zXK%Xy0T#H)G#I>aMH^jXt_RHXIVy2m1r8kt8b zlsvM)26eOxf@o2P(RjVL597YxTM>1c6t5$?5Z|}cm(C0y67FBxCIUok0e%! z1q6vo;y*{Bw<19jk&xixX$LV-ju8h`_``-$A9XKnXvjLev?3dfb+kA)oX_T~>j&4B zeBhVsS8Z}Z{9vE;v0kD{A6K<;W7(RToy%X@t(a;?#_3m71d=d zr88!fe56BP*tGOndG|ibj7L*dk}AnwoLzh*Np(r38_;c7iA}|;;_Dp>3t=TEJpI~r z{<7db|2;N1n_qc_-DUmXtfGTex?3oJl>kBV_y|i3)Zn_^khGgpGspRqew?IHzHSo#TQyGx5gRii-F_MsE0LJ8HDK%KO7vN z=)Q^Zk37zqD}*sZRyOyLIXZCi|MkYgU!|Z0+}FzLTESWkb>4%t{>pt!$0w&_It zrT`QrFpzq5Oj90WqXHW%m!WcfaxK0RZr6`NU$fe)(VN3h?JY1EKs=_k%Q! zP9eW#N&!bqjsBeZBVi8HED065%~IRz`*v+MR&l{WITjc1yc$+%U{1ulx>(L_j-5`F zBI@KNG@2pqBlxX4M5PPLqBGtu02z&KRd@(_s|}(yaw*vPr*vf)rOO^#Rz%PS03`@Q z-x)4v{Urc3u$hKgyu7xLQ*UCp#SXA-k}faPU|jZk)uA?JG-e^W3}ERj8cM)T7JdlL zc0nF0>^2_+z=^}CR!7HJ8W`gx4sn#+=AzmlO2ii1VMK!!x8p~J@vPp`#>bf1Vop-J zSN+L@UONE58;qgw^rB=ZqVpoLiU4n`bOz=R3{4DRFeL{I#;e+}RnIUHMTf@5X34P# zyeDYUU{Pf^`hEE`^k!HO4$wS)>Zqflrr1&uCCn2(c@dEv$)apab>b6-i(Cyl>hX?k z19X@S$2tMYv1q+UAp=`!h+?(ru}wQ{PhhM7i_e@UMfSefAGBzX+@_c-w9J+C+hHce zXHZhbH?tuV8%K`oWFcKu#Q3$u4eT`2lbIik&K0Hr4#i+7aUDcgFgercTF)0OY@+^) zpPtAlq6CI0>(-tdyx?aBA!I#VnF6-udYqv!UWOf6jbBL=Xl)Y+1d zzdR6c(;mluLVjc8Dm3dXhw6v=6=G$*Gm;B{0mhTq2#HgD=qI%72AG9`y6ED_d=yk& ztg36IPf%RY8VLK9#lFxiOXpMrX#y=mC1wHr;93@5Qz~fjp=vP>vIqQt5AbL43BPDY zQJr$`5|XI~eg@|ob^TEcPx`I-%i|Rzel!R$w4!9&F|0+%8xm#GWCg!Tx~>l79{V`B z?Xs!xw*-FMNcAE^?`Zc!23SNnrmZd>8-;J*(q|Wdx#w$mV6?{& ZAW|PCr`Wg(XLM`IW|Ri_`Ahlk@&R}sbiDuo literal 0 HcmV?d00001 diff --git a/front/app/src/assets/i18n/en-US.json b/front/app/src/assets/i18n/en-US.json new file mode 100644 index 00000000..46887c96 --- /dev/null +++ b/front/app/src/assets/i18n/en-US.json @@ -0,0 +1,145 @@ +{ + "menu": { + "dashboard": "Dashboard", + "design": "Design", + "design.colors": "Color System", + "design.icons": "Material Icons", + "material": "Material", + "material.form-controls": "Form Controls", + "material.form-controls.autocomplete": "Autocomplete", + "material.form-controls.checkbox": "Checkbox", + "material.form-controls.datepicker": "Datepicker", + "material.form-controls.form-field": "Form Field", + "material.form-controls.input": "Input", + "material.form-controls.radio": "Radio", + "material.form-controls.select": "Select", + "material.form-controls.slider": "Slider", + "material.form-controls.slide-toggle": "Slide Toggle", + "material.navigation": "Navigation", + "material.navigation.menu": "Menu", + "material.navigation.sidenav": "Sidenav", + "material.navigation.toolbar": "Toolbar", + "material.layout": "Layout", + "material.layout.card": "Card", + "material.layout.divider": "Divider", + "material.layout.expansion": "Expansion Panel", + "material.layout.grid-list": "Grid List", + "material.layout.list": "List", + "material.layout.stepper": "Stepper", + "material.layout.tab": "Tab", + "material.layout.tree": "Tree", + "material.buttons-indicators": "Buttons & Indicators", + "material.buttons-indicators.button": "Buttons", + "material.buttons-indicators.button-toggle": "Button Toggle", + "material.buttons-indicators.badge": "Badge", + "material.buttons-indicators.chips": "Chips", + "material.buttons-indicators.icon": "Icon", + "material.buttons-indicators.progress-spinner": "Progress Spinner", + "material.buttons-indicators.progress-bar": "Progress Bar", + "material.buttons-indicators.ripple": "Ripple", + "material.popups-modals": "Popups & Modals", + "material.popups-modals.bottom-sheet": "Bottom Sheet", + "material.popups-modals.dialog": "Dialog", + "material.popups-modals.snackbar": "Snackbar", + "material.popups-modals.tooltip": "Tooltip", + "material.data-table": "Data Table", + "material.data-table.paginator": "Paginator", + "material.data-table.sort": "Sort", + "material.data-table.table": "Table", + "media": "Media", + "media.gallery": "Gallery", + "forms": "Forms", + "forms.form-elements": "Form Elements", + "forms.dynamic-form": "Dynamic Form", + "forms.select": "Select", + "forms.datetime": "Date Time", + "tables": "Tables", + "tables.kitchen-sink": "Kitchen Sink", + "tables.remote-data": "Remote Data", + "profile": "Profile", + "profile.overview": "Overview", + "profile.settings": "Settings", + "extensions": "Extensions", + "sessions": "Sessions", + "sessions.403": "403", + "sessions.404": "404", + "sessions.500": "500", + "utilities": "Utilities", + "utilities.css-grid": "CSS Grid", + "utilities.css-helpers": "CSS Helpers", + "menu-level": "Menu Level", + "menu-level.level-1-1": "Level 1.1", + "menu-level.level-1-2": "Level 1.2", + "menu-level.level-1-1.level-2-1": "Level 2.1", + "menu-level.level-1-1.level-2-2": "Level 2.2", + "menu-level.level-1-1.level-2-1.level-3-1": "Level 3.1", + "menu-level.level-1-1.level-2-1.level-3-1.level-4-1": "Level 4.1", + "permissions": "Permissions", + "permissions.role-switching": "Role Switching", + "permissions.route-guard": "Route Guard", + "permissions.test": "Permission Test" + }, + "validations": { + "required": "This field is required", + "minlength": "Should have atleast {{number}} characters", + "maxlength": "This value should be less than {{number}} characters", + "min": "This value should be more than {{number}}", + "max": "This value should be less than {{number}}", + "exist": "The {{value}} has exists", + "inconsistent": "Inconsistent with {{value}}", + "invalid_email": "Invalid email" + }, + "user": { + "profile": "profile", + "settings": "settings", + "logout": "logout" + }, + "login": { + "title": "Welcome Back", + "username": "Username", + "password": "Password", + "remember_me": "Remember Me", + "login": "Login", + "have_no_account": "Don't have an account", + "create_one": "Click here to create one", + "please_enter": "Please enter" + }, + "register": { + "welcome": "Welcome", + "title": "It only takes a few seconds to create your account", + "confirm_password": "Confirm Password", + "agree": "I have read and agree to the terms of service", + "have_an_account": "Already have an account", + "register": "Create account" + }, + "paginator": { + "items_per_page_label": "Items per page:", + "next_page_label": "Next page", + "previous_page_label": "Previous page", + "first_page_label": "First page", + "last_page_label": "Last page", + "range_page_label_1": "no record", + "range_page_label_2": "{{startIndex}} - {{endIndex}} of {{length}}" + }, + "table_kitchen_sink": { + "position": "Position", + "name": "Name", + "weight": "Weight", + "symbol": "Symbol", + "gender": "Gender", + "mobile": "Mobile", + "tele": "Telephone", + "birthday": "Birthday", + "city": "City", + "address": "Address", + "website": "Website", + "company": "Company", + "email": "Email", + "operation": "Operation", + "edit": "Edit", + "delete": "Delete", + "confirm_delete": "Confirm delete?", + "ok": "Ok", + "close": "Close" + } +} diff --git a/front/app/src/assets/i18n/zh-CN.json b/front/app/src/assets/i18n/zh-CN.json new file mode 100644 index 00000000..4dc1d60a --- /dev/null +++ b/front/app/src/assets/i18n/zh-CN.json @@ -0,0 +1,145 @@ +{ + "menu": { + "dashboard": "数据大盘", + "design": "设计", + "design.colors": "颜色系统", + "design.icons": "Material 图标库", + "material": "Material", + "material.form-controls": "表单控件", + "material.form-controls.autocomplete": "自动完成", + "material.form-controls.checkbox": "检查框", + "material.form-controls.datepicker": "日期选择器", + "material.form-controls.form-field": "表单字段", + "material.form-controls.input": "输入框", + "material.form-controls.radio": "单选按钮", + "material.form-controls.select": "选择框", + "material.form-controls.slider": "滑竿", + "material.form-controls.slide-toggle": "滑块开关", + "material.navigation": "导航", + "material.navigation.menu": "菜单", + "material.navigation.sidenav": "侧边栏", + "material.navigation.toolbar": "工具栏", + "material.layout": "布局", + "material.layout.card": "卡片", + "material.layout.divider": "分割器", + "material.layout.expansion": "可展开面板", + "material.layout.grid-list": "网格列表", + "material.layout.list": "列表", + "material.layout.stepper": "步进器", + "material.layout.tab": "选项卡", + "material.layout.tree": "树", + "material.buttons-indicators": "按钮与指示器", + "material.buttons-indicators.button": "按钮", + "material.buttons-indicators.button-toggle": "开关按钮", + "material.buttons-indicators.badge": "徽章", + "material.buttons-indicators.chips": "芯片", + "material.buttons-indicators.icon": "图标", + "material.buttons-indicators.progress-spinner": "进度圈", + "material.buttons-indicators.progress-bar": "进度条", + "material.buttons-indicators.ripple": "水波", + "material.popups-modals": "弹框与模态框", + "material.popups-modals.bottom-sheet": "底部操作表", + "material.popups-modals.dialog": "对话框", + "material.popups-modals.snackbar": "快餐栏", + "material.popups-modals.tooltip": "提示框", + "material.data-table": "数据表", + "material.data-table.paginator": "分页器", + "material.data-table.sort": "排序头", + "material.data-table.table": "表格", + "media": "媒体", + "media.gallery": "图片画廊", + "forms": "表单", + "forms.form-elements": "表单元素", + "forms.dynamic-form": "动态表单", + "forms.select": "选择框", + "forms.datetime": "日期时间", + "tables": "表格", + "tables.kitchen-sink": "基础演示", + "tables.remote-data": "远程数据", + "profile": "个人信息", + "profile.overview": "概述", + "profile.settings": "设置", + "extensions": "扩展组件库", + "sessions": "会话", + "sessions.403": "403", + "sessions.404": "404", + "sessions.500": "500", + "utilities": "辅助工具", + "utilities.css-grid": "CSS 栅格", + "utilities.css-helpers": "CSS 辅助类", + "menu-level": "菜单层级", + "menu-level.level-1-1": "层级 1.1", + "menu-level.level-1-2": "层级 1.2", + "menu-level.level-1-1.level-2-1": "层级 2.1", + "menu-level.level-1-1.level-2-2": "层级 2.2", + "menu-level.level-1-1.level-2-1.level-3-1": "层级 3.1", + "menu-level.level-1-1.level-2-1.level-3-1.level-4-1": "层级 4.1", + "permissions": "权限管理", + "permissions.role-switching": "切换角色", + "permissions.route-guard": "路由守卫", + "permissions.test": "权限测试" + }, + "validations": { + "required": "该字段必填", + "minlength": "必须大于 {{number}} 个字符", + "maxlength": "必须小于 {{number}} 个字符", + "min": "值必须大于 {{number}}", + "max": "值必须小于 {{number}}", + "exist": "{{value}} 已存在", + "inconsistent": "与 {{value}} 不一致", + "invalid_email": "邮箱格式不正确" + }, + "user": { + "profile": "个人信息", + "settings": "偏好设置", + "logout": "退出" + }, + "login": { + "title": "欢迎回来", + "username": "用户名", + "password": "密码", + "remember_me": "记住我", + "login": "登入", + "have_no_account": "没有账号", + "create_one": "点此创建一个", + "please_enter": "请输入" + }, + "register": { + "welcome": "欢迎", + "title": "创建您的帐户只需几秒钟", + "confirm_password": "确认密码", + "agree": "我已阅读并同意服务条款", + "have_an_account": "已经有帐号了", + "register": "创建账号" + }, + "paginator": { + "items_per_page_label": "每页共:", + "next_page_label": "下一页", + "previous_page_label": "前一页", + "first_page_label": "首页", + "last_page_label": "尾页", + "range_page_label_1": "无数据", + "range_page_label_2": "第 {{startIndex}} - {{endIndex}} 行,共 {{length}} 行" + }, + "table_kitchen_sink": { + "position": "序号", + "name": "姓名", + "weight": "体重", + "symbol": "代号", + "gender": "性别", + "mobile": "手机号", + "tele": "固话", + "birthday": "出生日期", + "city": "城市", + "address": "家庭地址", + "company": "公司", + "website": "网址", + "email": "邮箱", + "operation": "操作", + "edit": "编辑", + "delete": "删除", + "confirm_delete": "确认删除?", + "ok": "确定", + "close": "关闭" + } +} diff --git a/front/app/src/assets/i18n/zh-TW.json b/front/app/src/assets/i18n/zh-TW.json new file mode 100644 index 00000000..fcc2eb54 --- /dev/null +++ b/front/app/src/assets/i18n/zh-TW.json @@ -0,0 +1,145 @@ +{ + "menu": { + "dashboard": "數據大盤", + "design": "設計", + "design.colors": "颜色系统", + "design.icons": "Material 圖標庫", + "material": "Material", + "material.form-controls": "表單控件", + "material.form-controls.autocomplete": "自動完成", + "material.form-controls.checkbox": "檢查框", + "material.form-controls.datepicker": "日期選擇器", + "material.form-controls.form-field": "表單字段", + "material.form-controls.input": "輸入框", + "material.form-controls.radio": "單選按鈕", + "material.form-controls.select": "選擇框", + "material.form-controls.slider": "滑竿", + "material.form-controls.slide-toggle": "滑塊開關", + "material.navigation": "導航", + "material.navigation.menu": "菜單", + "material.navigation.sidenav": "側邊欄", + "material.navigation.toolbar": "工具欄", + "material.layout": "布局", + "material.layout.card": "卡片", + "material.layout.divider": "分割器", + "material.layout.expansion": "可展開面板", + "material.layout.grid-list": "網格列表", + "material.layout.list": "列表", + "material.layout.stepper": "步進器", + "material.layout.tab": "選項卡", + "material.layout.tree": "樹", + "material.buttons-indicators": "按鈕與指示器", + "material.buttons-indicators.button": "按鈕", + "material.buttons-indicators.button-toggle": "開關按鈕", + "material.buttons-indicators.badge": "徽章", + "material.buttons-indicators.chips": "芯片", + "material.buttons-indicators.icon": "圖標", + "material.buttons-indicators.progress-spinner": "進度圈", + "material.buttons-indicators.progress-bar": "進度條", + "material.buttons-indicators.ripple": "水波", + "material.popups-modals": "彈框與模態框", + "material.popups-modals.bottom-sheet": "底部操作表", + "material.popups-modals.dialog": "對話框", + "material.popups-modals.snackbar": "快餐欄", + "material.popups-modals.tooltip": "提示框", + "material.data-table": "數據表", + "material.data-table.paginator": "分頁器", + "material.data-table.sort": "排序頭", + "material.data-table.table": "表格", + "media": "媒體", + "media.gallery": "圖片畫廊", + "forms": "表單", + "forms.form-elements": "表單元素", + "forms.dynamic-form": "動態表單", + "forms.select": "選擇框", + "forms.datetime": "日期時間", + "tables": "表格", + "tables.kitchen-sink": "基礎演示", + "tables.remote-data": "遠程數據", + "profile": "個人信息", + "profile.overview": "概述", + "profile.settings": "設置", + "extensions": "擴展組件庫", + "sessions": "會話", + "sessions.403": "403", + "sessions.404": "404", + "sessions.500": "500", + "utilities": "輔助工具", + "utilities.css-grid": "CSS 柵格", + "utilities.css-helpers": "CSS 輔助類", + "menu-level": "菜單層級", + "menu-level.level-1-1": "層級 1.1", + "menu-level.level-1-2": "層級 1.2", + "menu-level.level-1-1.level-2-1": "層級 2.1", + "menu-level.level-1-1.level-2-2": "層級 2.2", + "menu-level.level-1-1.level-2-1.level-3-1": "層級 3.1", + "menu-level.level-1-1.level-2-1.level-3-1.level-4-1": "層級 4.1", + "permissions": "權限管理", + "permissions.role-switching": "切換角色", + "permissions.route-guard": "路由守衛", + "permissions.test": "權限測試" + }, + "validations": { + "required": "該字段必填", + "minlength": "必須大于 {{number}} 個字符", + "maxlength": "必須小于 {{number}} 個字符", + "min": "值必须大于 {{number}}", + "max": "值必須小于 {{number}}", + "exist": "{{value}} 已存在", + "inconsistent": "與 {{value}} 不壹致", + "invalid_email": "郵箱格式不正確" + }, + "user": { + "profile": "個人信息", + "settings": "偏好設置", + "logout": "退出" + }, + "login": { + "title": "歡迎回來", + "username": "用戶名", + "password": "密碼", + "remember_me": "記住我", + "login": "登入", + "have_no_account": "沒有賬號", + "create_one": "點此創建壹個", + "please_enter": "請輸入" + }, + "register": { + "welcome": "歡迎", + "title": "創建您的帳戶只需幾秒鐘", + "confirm_password": "確認密碼", + "agree": "我已閱讀並同意服務條款", + "have_an_account": "已經有帳號了", + "register": "創建賬號" + }, + "paginator": { + "items_per_page_label": "每頁共:", + "next_page_label": "下壹頁", + "previous_page_label": "前壹頁", + "first_page_label": "首頁", + "last_page_label": "尾頁", + "range_page_label_1": "無數據", + "range_page_label_2": "第 {{startIndex}} - {{endIndex}} 行,共 {{length}} 行" + }, + "table_kitchen_sink": { + "position": "序號", + "name": "姓名", + "weight": "體重", + "symbol": "代號", + "gender": "性别", + "mobile": "手機號", + "tele": "固話", + "birthday": "出生日期", + "city": "城市", + "address": "家庭地址", + "company": "公司", + "website": "網址", + "email": "郵箱", + "operation": "操作", + "edit": "編輯", + "delete": "删除", + "confirm_delete": "確認刪除?", + "ok": "確定", + "close": "關閉" + } +} diff --git a/front/app/src/assets/images/avatar-default.jpg b/front/app/src/assets/images/avatar-default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ead43e1993e9bdb2ab3971d9116653ef19d73cc GIT binary patch literal 11400 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 DoBcKV literal 0 HcmV?d00001 diff --git a/front/app/src/assets/images/matero.png b/front/app/src/assets/images/matero.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3f799d7a99f07f8d531c15c574ea745f323d0e GIT binary patch literal 9782 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 R=R+u literal 0 HcmV?d00001 diff --git a/front/app/src/environments/environment.prod.ts b/front/app/src/environments/environment.prod.ts new file mode 100644 index 00000000..b1e84509 --- /dev/null +++ b/front/app/src/environments/environment.prod.ts @@ -0,0 +1,5 @@ +export const environment = { + production: true, + baseUrl: '', + useHash: false, +}; diff --git a/front/app/src/environments/environment.ts b/front/app/src/environments/environment.ts new file mode 100644 index 00000000..ab0f6159 --- /dev/null +++ b/front/app/src/environments/environment.ts @@ -0,0 +1,18 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + baseUrl: '', + useHash: false, +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/front/app/src/index.html b/front/app/src/index.html index 48121633..01327d0d 100644 --- a/front/app/src/index.html +++ b/front/app/src/index.html @@ -7,10 +7,73 @@ - + + - + +

LOADING

diff --git a/front/app/src/styles.css b/front/app/src/styles.css index 7e7239a2..90d4ee00 100644 --- a/front/app/src/styles.css +++ b/front/app/src/styles.css @@ -1,4 +1 @@ /* You can add global styles to this file, and also import other style files */ - -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } diff --git a/front/app/src/styles.scss b/front/app/src/styles.scss new file mode 100644 index 00000000..f3d3af70 --- /dev/null +++ b/front/app/src/styles.scss @@ -0,0 +1,7 @@ +/* You can add global styles to this file, and also import other style files */ +@use 'app/theme/style'; +@use 'styles/themes'; +@use 'styles/custom'; +@use 'styles/grid'; +@use 'styles/helpers'; +@use 'styles/plugins'; diff --git a/front/app/src/styles/_app-theme.scss b/front/app/src/styles/_app-theme.scss new file mode 100644 index 00000000..99b73bad --- /dev/null +++ b/front/app/src/styles/_app-theme.scss @@ -0,0 +1,28 @@ +@use '@angular/material' as mat; +@use '@ng-matero/extensions' as mtx; + +@use '../app/theme/style/reboot-theme'; +@use '../app/theme/header/header-theme'; +@use '../app/theme/sidebar/sidebar-theme'; +@use '../app/theme/sidemenu/sidemenu-theme'; +@use '../app/theme/topmenu/topmenu-theme'; +@use '../app/shared/components/error-code/error-code-theme'; + +@use './custom/table-theme'; + +@include mat.core(); + +// Styles for the app that are based on the current theme. +@mixin theme($theme) { + @include mat.all-component-themes($theme); + @include mtx.all-component-themes($theme); + + @include reboot-theme.theme($theme); + @include header-theme.theme($theme); + @include sidebar-theme.theme($theme); + @include sidemenu-theme.theme($theme); + @include topmenu-theme.theme($theme); + @include error-code-theme.theme($theme); + + @include table-theme.theme($theme); +} diff --git a/front/app/src/styles/_custom.scss b/front/app/src/styles/_custom.scss new file mode 100644 index 00000000..a79b8075 --- /dev/null +++ b/front/app/src/styles/_custom.scss @@ -0,0 +1,2 @@ +@use 'custom/material'; +@use 'custom/table'; diff --git a/front/app/src/styles/_grid.scss b/front/app/src/styles/_grid.scss new file mode 100644 index 00000000..867a3ea8 --- /dev/null +++ b/front/app/src/styles/_grid.scss @@ -0,0 +1 @@ +@use 'grid/grid'; diff --git a/front/app/src/styles/_helpers.scss b/front/app/src/styles/_helpers.scss new file mode 100644 index 00000000..5fbeb09f --- /dev/null +++ b/front/app/src/styles/_helpers.scss @@ -0,0 +1 @@ +@use 'helpers/api'; diff --git a/front/app/src/styles/_plugins.scss b/front/app/src/styles/_plugins.scss new file mode 100644 index 00000000..58e3224b --- /dev/null +++ b/front/app/src/styles/_plugins.scss @@ -0,0 +1,2 @@ +@use 'plugins/ngx-formly'; +@use 'plugins/ngx-toastr'; diff --git a/front/app/src/styles/_themes.scss b/front/app/src/styles/_themes.scss new file mode 100644 index 00000000..2197dc91 --- /dev/null +++ b/front/app/src/styles/_themes.scss @@ -0,0 +1,46 @@ +@use '@angular/material' as mat; +@use 'app-theme'; + +// Define the palettes for your theme using the Material Design palettes available in palette.scss +// (imported above). For each palette, you can optionally specify a default, lighter, and darker +// hue. Available color palettes: https://material.io/design/color/ + +// +// Light theme +// +$primary: mat.define-palette(mat.$indigo-palette); +$accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); +$theme: mat.define-light-theme( + ( + color: ( + primary: $primary, + accent: $accent + ), + typography: mat.define-typography-config(), + density: 0 + ) +); + +@include app-theme.theme($theme); + +// +// Dark theme +// +.theme-dark { + color-scheme: dark; + + $primary: mat.define-palette(mat.$blue-palette); + $accent: mat.define-palette(mat.$amber-palette, A200, A100, A400); + $theme: mat.define-dark-theme( + ( + color: ( + primary: $primary, + accent: $accent + ), + typography: null, + density: null + ) + ); + + @include app-theme.theme($theme); +} diff --git a/front/app/src/styles/custom/_material.scss b/front/app/src/styles/custom/_material.scss new file mode 100644 index 00000000..dd3f6e00 --- /dev/null +++ b/front/app/src/styles/custom/_material.scss @@ -0,0 +1,18 @@ +/** Override Material styles */ + +@use '../../app/theme/style/variables'; + +.mat-mdc-card { + margin-bottom: variables.$gutter; +} + +.mat-mdc-accordion { + display: block; + margin-bottom: variables.$gutter; +} + +.form-field-full { + .mat-mdc-form-field { + width: 100%; + } +} diff --git a/front/app/src/styles/custom/_mixins.scss b/front/app/src/styles/custom/_mixins.scss new file mode 100644 index 00000000..691683c4 --- /dev/null +++ b/front/app/src/styles/custom/_mixins.scss @@ -0,0 +1,5 @@ +@mixin dark { + @at-root .theme-dark #{&} { + @content; + } +} diff --git a/front/app/src/styles/custom/_table-theme.scss b/front/app/src/styles/custom/_table-theme.scss new file mode 100644 index 00000000..149211a1 --- /dev/null +++ b/front/app/src/styles/custom/_table-theme.scss @@ -0,0 +1,40 @@ +@use 'sass:color'; +@use 'sass:map'; +@use '@angular/material' as mat; + +// Example for custom theming +@mixin theme($theme) { + $is-dark-theme: map.get($theme, is-dark); + $background: map.get($theme, 'background'); + $foreground: map.get($theme, 'foreground'); + + $row-hover-bg: mat.get-color-from-palette(mat.$indigo-palette, 50); + $row-odd-bg: mat.get-color-from-palette(mat.$gray-palette, 100); + + $row-hover-bg-dark: mat.get-color-from-palette(mat.$blue-gray-palette, 900); + $row-odd-bg-dark: color.adjust(mat.get-color-from-palette(mat.$gray-palette, 900), $lightness: 10%); + + mtx-grid.mtx-grid { + .mat-table { + &.mat-table-striped { + .mat-row-odd { + background-color: if($is-dark-theme, $row-odd-bg-dark, $row-odd-bg); + } + } + + &.mat-table-hover { + .mat-row { + &:hover { + background-color: if($is-dark-theme, $row-hover-bg-dark, $row-hover-bg); + } + } + } + + .mat-row { + &.selected { + background-color: if($is-dark-theme, $row-hover-bg-dark, $row-hover-bg); + } + } + } + } +} diff --git a/front/app/src/styles/custom/_table.scss b/front/app/src/styles/custom/_table.scss new file mode 100644 index 00000000..a9e7c66f --- /dev/null +++ b/front/app/src/styles/custom/_table.scss @@ -0,0 +1,7 @@ +/** Custom table style not with theme */ + +mtx-grid.mtx-grid { + .mat-header-cell { + white-space: nowrap; + } +} diff --git a/front/app/src/styles/grid/_grid.scss b/front/app/src/styles/grid/_grid.scss new file mode 100644 index 00000000..db4bc5c4 --- /dev/null +++ b/front/app/src/styles/grid/_grid.scss @@ -0,0 +1,40 @@ +@use 'variables'; +@use 'mixins'; + +.row { + display: flex; + flex-wrap: wrap; + margin-right: variables.$grid-gutter * -.5; + margin-left: variables.$grid-gutter * -.5; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; + + > .col, + > [class*='col-'] { + padding-right: 0; + padding-left: 0; + } +} + +@include mixins.make-grid-columns(); + +// xsmall: 0, +// small: 600px +// medium: 960px +// large: 1280px +// xlarge: 1920px + +@each $breakpoint, $infix in variables.$breakpoint-infixs { + @if ($breakpoint== 'xsmall') { + @include mixins.loop-grid-columns(variables.$grid-columns, $infix); + } + + @else { + @include mixins.bp-gt($breakpoint) { + @include mixins.loop-grid-columns(variables.$grid-columns, $infix); + } + } +} diff --git a/front/app/src/styles/grid/_mixins.scss b/front/app/src/styles/grid/_mixins.scss new file mode 100644 index 00000000..dde3026f --- /dev/null +++ b/front/app/src/styles/grid/_mixins.scss @@ -0,0 +1,79 @@ +@use 'sass:map'; +@use 'sass:math'; +@use 'variables'; + +@function bp($name, $breakpoints: variables.$breakpoints) { + $min: map.get($breakpoints, $name); + + @return $min; +} + +// Media of at least the minimum breakpoint width. +@mixin bp-gt($name, $breakpoints: variables.$breakpoints) { + $min: bp($name, $breakpoints); + + @if $min { + @media (min-width: $min) { + @content; + } + } + @else { + @content; + } +} + +// Media of at most the maximum breakpoint width. +@mixin bp-lt($name, $breakpoints: variables.$breakpoints) { + $max: bp($name, $breakpoints) - 1px; + + @if $max { + @media (max-width: $max) { + @content; + } + } + @else { + @content; + } +} + +@mixin make-grid-columns($i: 1, $list: '.col') { + @each $breakpoint, $infix in variables.$breakpoint-infixs { + $infix: if($infix == '', '', '-#{$infix}'); + + @if ($infix != '') { + $list: '#{$list}, .col#{$infix}'; + } + + @for $i from 1 through variables.$grid-columns { + $list: '#{$list}, .col#{$infix}-#{$i}'; + } + } + + #{$list} { + position: relative; + width: 100%; + padding-right: variables.$grid-gutter * .5; + padding-left: variables.$grid-gutter * .5; + } +} + +@mixin loop-grid-columns($columns: $grid-columns, $breakpoint-infix: '') { + $infix: if($breakpoint-infix == '', '', '-#{$breakpoint-infix}'); + + .col#{$infix} { + flex-basis: 0; + flex-grow: 1; + max-width: 100%; + } + + @for $i from 1 through $columns { + .col#{$infix}-#{$i} { + flex: 0 0 math.percentage(math.div($i, $columns)); + max-width: math.percentage(math.div($i, $columns)); + } + + .offset#{$infix}-#{$i} { + margin-left: math.percentage(math.div($i, $columns)); + } + } +} diff --git a/front/app/src/styles/grid/_variables.scss b/front/app/src/styles/grid/_variables.scss new file mode 100644 index 00000000..3f353eed --- /dev/null +++ b/front/app/src/styles/grid/_variables.scss @@ -0,0 +1,18 @@ +$grid-columns: 12 !default; +$grid-gutter: 16px !default; + +$breakpoints: ( + xsmall: 0, + small: 600px, + medium: 960px, + large: 1280px, + xlarge: 1920px +) !default; + +$breakpoint-infixs: ( + xsmall: '', + small: 'sm', + medium: 'md', + large: 'lg', + xlarge: 'xl' +) !default; diff --git a/front/app/src/styles/helpers/_alignment.scss b/front/app/src/styles/helpers/_alignment.scss new file mode 100644 index 00000000..701fd25f --- /dev/null +++ b/front/app/src/styles/helpers/_alignment.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'vertical-align': ( + property: vertical-align, + class: align, + values: top middle bottom + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_api.scss b/front/app/src/styles/helpers/_api.scss new file mode 100644 index 00000000..edff6cf5 --- /dev/null +++ b/front/app/src/styles/helpers/_api.scss @@ -0,0 +1,47 @@ +@use 'sass:map'; +@use 'sass:meta'; +@use 'sass:list'; + +@use 'variables' as *; + +@use 'display'; +@use 'flexbox'; +@use 'position'; +@use 'sizing'; +@use 'spacing'; +@use 'border'; +@use 'rounded'; +@use 'text'; +@use 'alignment'; +@use 'overflow'; +@use 'cursor'; +@use 'image'; +@use 'icon'; + +// Generate Helpers +@each $key, $utility in $utilities { + $values: map.get($utility, values); + + // If the values are a list or string, convert it into a map + @if meta.type-of($values) == 'string' or meta.type-of(list.nth($values, 1)) != 'list' { + $values: list.zip($values, $values); + } + + $properties: map.get($utility, property); + $property-class-prefix: map.get($utility, class); + + // Utility class maybe empty, (e.g. with position class) + $property-class-prefix-hyphen: if( + $property-class-prefix == '', + $property-class-prefix, + $property-class-prefix + '-' + ); + + @each $class-modifier, $value in $values { + .#{$property-class-prefix-hyphen + $class-modifier} { + @each $property in $properties { + #{$property}: $value !important; + } + } + } +} diff --git a/front/app/src/styles/helpers/_border.scss b/front/app/src/styles/helpers/_border.scss new file mode 100644 index 00000000..798bd76b --- /dev/null +++ b/front/app/src/styles/helpers/_border.scss @@ -0,0 +1,33 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'border': ( + property: border, + class: b, + values: $borders + ), + 'border-top': ( + property: border-top, + class: b-t, + values: $borders + ), + 'border-bottom': ( + property: border-bottom, + class: b-b, + values: $borders + ), + 'border-right': ( + property: border-right, + class: b-r, + values: $borders + ), + 'border-left': ( + property: border-left, + class: b-l, + values: $borders + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_cursor.scss b/front/app/src/styles/helpers/_cursor.scss new file mode 100644 index 00000000..30fde211 --- /dev/null +++ b/front/app/src/styles/helpers/_cursor.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'cursor': ( + property: cursor, + class: cursor, + values: default pointer text move copy not-allowed + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_display.scss b/front/app/src/styles/helpers/_display.scss new file mode 100644 index 00000000..116c24c9 --- /dev/null +++ b/front/app/src/styles/helpers/_display.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'display': ( + property: display, + class: d, + values: block inline-block inline flex inline-flex none + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_flexbox.scss b/front/app/src/styles/helpers/_flexbox.scss new file mode 100644 index 00000000..7e043d86 --- /dev/null +++ b/front/app/src/styles/helpers/_flexbox.scss @@ -0,0 +1,90 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'flex': ( + property: flex, + class: flex, + values: (fill: 1 1 auto) + ), + 'flex-direction': ( + property: flex-direction, + class: flex, + values: ( + row: row, + row-reverse: row-reverse, + col: column, + col-reverse: column-reverse + ) + ), + 'flex-grow': ( + property: flex-grow, + class: flex, + values: ( + grow-0: 0, + grow-1: 1, + ) + ), + 'flex-shrink': ( + property: flex-shrink, + class: flex, + values: ( + shrink-0: 0, + shrink-1: 1, + ) + ), + 'flex-wrap': ( + property: flex-wrap, + class: flex, + values: wrap nowrap wrap-reverse + ), + 'justify-content': ( + property: justify-content, + class: justify-content, + values: ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around, + ) + ), + 'align-content': ( + property: align-content, + class: align-content, + values: ( + start: flex-start, + end: flex-end, + center: center, + between: space-between, + around: space-around, + stretch: stretch, + ) + ), + 'align-items': ( + property: align-items, + class: align-items, + values: ( + start: flex-start, + end: flex-end, + center: center, + baseline: baseline, + stretch: stretch, + ) + ), + 'align-self': ( + property: align-self, + class: align-self, + values: ( + auto: auto, + start: flex-start, + end: flex-end, + center: center, + baseline: baseline, + stretch: stretch, + ) + ), + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_functions.scss b/front/app/src/styles/helpers/_functions.scss new file mode 100644 index 00000000..c97b3ed9 --- /dev/null +++ b/front/app/src/styles/helpers/_functions.scss @@ -0,0 +1,14 @@ +@use 'sass:map'; + +// It makes the value negative. +@function negativify-map($map) { + $result: (); + + @each $key, $value in $map { + @if $key !=0 { + $result: map.merge($result, ('-' + $key: (-$value))); + } + } + + @return $result; +} diff --git a/front/app/src/styles/helpers/_icon.scss b/front/app/src/styles/helpers/_icon.scss new file mode 100644 index 00000000..64f094dd --- /dev/null +++ b/front/app/src/styles/helpers/_icon.scss @@ -0,0 +1,18 @@ +@use 'variables' as *; +@use 'mixins' as *; + +.icon-18 { + @include icon(18); +} + +.icon-24 { + @include icon(24); +} + +.icon-36 { + @include icon(36); +} + +.icon-48 { + @include icon(48); +} diff --git a/front/app/src/styles/helpers/_image.scss b/front/app/src/styles/helpers/_image.scss new file mode 100644 index 00000000..3b8f816e --- /dev/null +++ b/front/app/src/styles/helpers/_image.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'object-fit': ( + property: object-fit, + class: object, + values: contain cover fill none scale-down + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_mixins.scss b/front/app/src/styles/helpers/_mixins.scss new file mode 100644 index 00000000..5630b9c4 --- /dev/null +++ b/front/app/src/styles/helpers/_mixins.scss @@ -0,0 +1,6 @@ +@mixin icon($size) { + width: $size * 1px !important; + height: $size * 1px !important; + font-size: $size * 1px !important; + line-height: $size * 1px !important; +} diff --git a/front/app/src/styles/helpers/_overflow.scss b/front/app/src/styles/helpers/_overflow.scss new file mode 100644 index 00000000..f3dcefae --- /dev/null +++ b/front/app/src/styles/helpers/_overflow.scss @@ -0,0 +1,23 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'overflow': ( + property: overflow, + class: overflow, + values: auto hidden visible scroll + ), + 'overflow-x': ( + property: overflow-x, + class: overflow-x, + values: auto hidden visible scroll + ), + 'overflow-y': ( + property: overflow-y, + class: overflow-y, + values: auto hidden visible scroll + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_position.scss b/front/app/src/styles/helpers/_position.scss new file mode 100644 index 00000000..6c535cee --- /dev/null +++ b/front/app/src/styles/helpers/_position.scss @@ -0,0 +1,13 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'position': ( + property: position, + class: '', + values: static fixed absolute relative sticky + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_rounded.scss b/front/app/src/styles/helpers/_rounded.scss new file mode 100644 index 00000000..7d20992c --- /dev/null +++ b/front/app/src/styles/helpers/_rounded.scss @@ -0,0 +1,33 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'border-radius': ( + property: border-radius, + class: r, + values: $radius + ), + 'border-top-left-radius': ( + property: border-top-left-radius, + class: r-t-l, + values: $radius + ), + 'border-top-right-radius': ( + property: border-top-right-radius, + class: r-t-r, + values: $radius + ), + 'border-bottom-right-radius': ( + property: border-bottom-right-radius, + class: r-b-r, + values: $radius + ), + 'border-bottom-left-radius': ( + property: border-bottom-left-radius, + class: r-b-l, + values: $radius + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_sizing.scss b/front/app/src/styles/helpers/_sizing.scss new file mode 100644 index 00000000..fd59d66b --- /dev/null +++ b/front/app/src/styles/helpers/_sizing.scss @@ -0,0 +1,18 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'width': ( + property: width, + class: w, + values: $sizes + ), + 'height': ( + property: height, + class: h, + values: $sizes + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_spacing.scss b/front/app/src/styles/helpers/_spacing.scss new file mode 100644 index 00000000..9c853387 --- /dev/null +++ b/front/app/src/styles/helpers/_spacing.scss @@ -0,0 +1,113 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'margin': ( + property: margin, + class: m, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-x': ( + property: margin-left margin-right, + class: m-x, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-y': ( + property: margin-top margin-bottom, + class: m-y, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-top': ( + property: margin-top, + class: m-t, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-right': ( + property: margin-right, + class: m-r, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-bottom': ( + property: margin-bottom, + class: m-b, + values: map.merge($spacers, (auto: auto)) + ), + 'margin-left': ( + property: margin-left, + class: m-l, + values: map.merge($spacers, (auto: auto)) + ), + 'negative-margin': ( + property: margin, + class: m, + values: $negative-spacers + ), + 'negative-margin-x': ( + property: margin-left margin-right, + class: m-x, + values: $negative-spacers + ), + 'negative-margin-y': ( + property: margin-top margin-bottom, + class: m-y, + values: $negative-spacers + ), + 'negative-margin-top': ( + property: margin-top, + class: m-t, + values: $negative-spacers + ), + 'negative-margin-right': ( + property: margin-right, + class: m-r, + values: $negative-spacers + ), + 'negative-margin-bottom': ( + property: margin-bottom, + class: m-b, + values: $negative-spacers + ), + 'negative-margin-left': ( + property: margin-left, + class: m-l, + values: $negative-spacers + ), + 'padding': ( + property: padding, + class: p, + values: $spacers + ), + 'padding-x': ( + property: padding-left padding-right, + class: p-x, + values: $spacers + ), + 'padding-y': ( + property: padding-top padding-bottom, + class: p-y, + values: $spacers + ), + 'padding-top': ( + property: padding-top, + class: p-t, + values: $spacers + ), + 'padding-right': ( + property: padding-right, + class: p-r, + values: $spacers + ), + 'padding-bottom': ( + property: padding-bottom, + class: p-b, + values: $spacers + ), + 'padding-left': ( + property: padding-left, + class: p-l, + values: $spacers + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_text.scss b/front/app/src/styles/helpers/_text.scss new file mode 100644 index 00000000..fbe56a26 --- /dev/null +++ b/front/app/src/styles/helpers/_text.scss @@ -0,0 +1,49 @@ +@use 'sass:map'; +@use 'variables' as *; + +$utilities: map.merge( + ( + 'font-size': ( + property: font-size, + class: f-s, + values: $font-sizes + ), + 'font-weight': ( + property: font-weight, + class: f-w, + values: $font-wieghts + ), + 'text-transform': ( + property: text-transform, + class: text, + values: capitalize uppercase lowercase + ), + 'text-align': ( + property: text-align, + class: text, + values: center right left + ), + 'white-space': ( + property: white-space, + class: text, + values: ( + wrap: normal, + nowrap: nowrap + ) + ), + 'text-overflow': ( + property: text-overflow, + class: text, + values: ellipsis + ), + 'color': ( + property: color, + class: text, + values: ( + reset: inherit, + current: currentColor + ) + ) + ), + $utilities +); diff --git a/front/app/src/styles/helpers/_variables.scss b/front/app/src/styles/helpers/_variables.scss new file mode 100644 index 00000000..de67e7c1 --- /dev/null +++ b/front/app/src/styles/helpers/_variables.scss @@ -0,0 +1,83 @@ +@use 'functions' as *; + +// Utility Map + +$utilities: () !default; + +// Spacing + +$spacer: 16px !default; +$spacers: ( + 0: 0, + 2: $spacer * .125, + 4: $spacer * .25, + 8: $spacer * .5, + 12: $spacer * .75, + 16: $spacer, + 24: $spacer * 1.5, + 32: $spacer * 2, + 48: $spacer * 3 +) !default; +$negative-spacers: negativify-map($spacers) !default; + +// Border + +$border-color: rgba(0, 0, 0, .12) !default; +$borders: ( + 0: 0, + 1: 1px solid $border-color, + 2: 2px solid $border-color, + 4: 4px solid $border-color, + 8: 8px solid $border-color +) !default; + +// Border radius + +$radius-base: 4px !default; +$radius: ( + 0: 0, + 4: $radius-base, + 8: $radius-base * 2, + 12: $radius-base * 3, + 16: $radius-base * 4, + full: 9999px +) !default; + +// Text + +$font-wieghts: ( + 100: 100, + 200: 200, + 300: 300, + 400: 400, + 500: 500, + 600: 600, + 700: 700, + 800: 800, + 900: 900 +) !default; + +$font-sizes: ( + 0: 0, + 10: 10px, + 12: 12px, + 14: 14px, + 16: 16px, + 18: 18px, + 20: 20px +) !default; + +// Sizing + +$sizes: ( + 0: 0, + 20: 20%, + 25: 25%, + 40: 40%, + 50: 50%, + 60: 60%, + 75: 75%, + 80: 80%, + full: 100%, + auto: auto +) !default; diff --git a/front/app/src/styles/plugins/_ngx-formly.scss b/front/app/src/styles/plugins/_ngx-formly.scss new file mode 100644 index 00000000..75754301 --- /dev/null +++ b/front/app/src/styles/plugins/_ngx-formly.scss @@ -0,0 +1,3 @@ +formly-wrapper-mat-form-field .mat-mdc-form-field { + width: 100%; +} diff --git a/front/app/src/styles/plugins/_ngx-toastr.scss b/front/app/src/styles/plugins/_ngx-toastr.scss new file mode 100644 index 00000000..8d737f72 --- /dev/null +++ b/front/app/src/styles/plugins/_ngx-toastr.scss @@ -0,0 +1,52 @@ +@use '@angular/material' as mat; +@use 'ngx-toastr/toastr'; + +.toast-container { + .ngx-toastr { + min-height: 48px; + padding: 14px 16px; + color: rgba(255, 255, 255, .7); + background-color: #323232; + border-radius: 4px; + + @include mat.elevation(6); + + &:hover { + @include mat.elevation(10); + } + } + + .toast-success, + .toast-info, + .toast-warning, + .toast-error { + padding-left: 50px; + color: #fff; + } + + .toast-info { + background-color: mat.get-color-from-palette(mat.$blue-palette, 500); + } + + .toast-success { + background-color: mat.get-color-from-palette(mat.$green-palette, 500); + } + + .toast-warning { + background-color: mat.get-color-from-palette(mat.$orange-palette, 500); + } + + .toast-error { + background-color: mat.get-color-from-palette(mat.$red-palette, 500); + } + + .toast-close-button { + font-size: inherit; + text-shadow: 0 1px 0 rgba(0, 0, 0, .25); + + &:hover { + color: inherit; + opacity: .6; + } + } +} diff --git a/front/app/src/styles/plugins/_photoviewer.scss b/front/app/src/styles/plugins/_photoviewer.scss new file mode 100644 index 00000000..bdeb7ac1 --- /dev/null +++ b/front/app/src/styles/plugins/_photoviewer.scss @@ -0,0 +1,3 @@ +// The photoviewer styles have been imported from the `@ng-matero/extensions/theming` +// so that you don't need to import it again. +// @use '~photoviewer/dist/photoviewer.css'; diff --git a/front/app/src/typings.d.ts b/front/app/src/typings.d.ts new file mode 100644 index 00000000..84c8d73c --- /dev/null +++ b/front/app/src/typings.d.ts @@ -0,0 +1 @@ +declare const ApexCharts: any; diff --git a/front/app/tsconfig.app.json b/front/app/tsconfig.app.json index 374cc9d2..c3317bcd 100644 --- a/front/app/tsconfig.app.json +++ b/front/app/tsconfig.app.json @@ -1,14 +1,9 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["node"] }, - "files": [ - "src/main.ts" - ], - "include": [ - "src/**/*.d.ts" - ] + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] } diff --git a/front/app/tsconfig.json b/front/app/tsconfig.json index ed966d43..1b2310ef 100644 --- a/front/app/tsconfig.json +++ b/front/app/tsconfig.json @@ -1,28 +1,36 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "./", + "baseUrl": "src", + "downlevelIteration": true, "outDir": "./dist/out-tsc", "forceConsistentCasingInFileNames": true, "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "sourceMap": true, "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, + "module": "es2020", "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, "importHelpers": true, "target": "ES2022", - "module": "ES2022", - "useDefineForClassFields": false, - "lib": [ - "ES2022", - "dom" - ] + "typeRoots": ["node_modules/@types"], + "lib": ["es2019", "dom"], + "paths": { + "@core": ["app/core"], + "@core/*": ["app/core/*"], + "@shared": ["app/shared"], + "@shared/*": ["app/shared/*"], + "@theme": ["app/theme"], + "@theme/*": ["app/theme/*"], + "@testing": ["testing"], + "@testing/*": ["testing/*"], + "@env": ["environments"], + "@env/*": ["environments/*"] + }, + "useDefineForClassFields": false }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, diff --git a/front/app/tsconfig.spec.json b/front/app/tsconfig.spec.json index be7e9da7..1030a83c 100644 --- a/front/app/tsconfig.spec.json +++ b/front/app/tsconfig.spec.json @@ -1,14 +1,9 @@ -/* To learn more about this file see: https://angular.io/config/tsconfig. */ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] + "types": ["jasmine", "node"] }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.d.ts" - ] + "files": ["src/test.ts"], + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] }