diff --git a/.gitignore b/.gitignore index fb34d256..b70ffb74 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,10 @@ logs .patches codex.patch .env +TODO .codex -.playwright-mcp \ No newline at end of file +.playwright-mcp +package-lock.json +refactor_docs/ +neutral-criteria-task.md +.claude/settings.json diff --git a/AGENTS.md b/AGENTS.md index a27ce48f..ca6ab83b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,122 @@ -### Agent Instructions - -* **Install Required Modules**: Ensure all necessary dependencies are installed before proceeding. -* **Run the Project**: Launch the development server to confirm that the application starts correctly. -* **Execute the Build Process**: Run the build to verify that everything compiles without errors. -* **Use TSX for Execution**: Follow the TSX-based startup method when running the project. -* **Write Documentation Immediately**: All relevant documentation must be added to the `docs/` directory as you work. -* **Follow Best Practices for Naming and Comments**: All variable names, function names, and comments must follow clean code principles and be written in English. +# Adminizer - Project Context + +## Project Overview + +Adminizer is a framework-agnostic admin panel for Node.js. +It includes a TypeScript backend and a React frontend, and can be integrated into server-side applications with different architectures. + +## Key Characteristics + +- Type: Node.js library/module +- Backend: TypeScript (ESM) +- Frontend: React + TypeScript + Vite +- UI stack: Radix UI, Tailwind CSS v4, shadcn/ui style components +- ORM support: Sequelize (active), TypeORM (experimental) +- License: MIT + +## Current Development Focus + +- Primary ORM: Sequelize +- TypeORM support exists but is experimental; keep Sequelize as the default recommendation +- For model adapter changes, prioritize `src/lib/model/adapter/sequelize.ts` + +## Directory Structure + +```text +adminizer/ +|-- src/ # Main source code +| |-- assets/ # Frontend (React/TSX/CSS) +| |-- controllers/ # Express controllers +| |-- helpers/ # Utilities +| |-- interfaces/ # Types/interfaces +| |-- lib/ # Core modules +| |-- models/ # Internal models +| |-- policies/ # Access policies +| |-- system/ # System wiring +| `-- index.ts # Main entry +|-- fixture/ # Demo app for local development +|-- modules/ # Extendable modules +|-- controls/ # Custom controls +|-- docs/ # Documentation +|-- test/ # Vitest tests +|-- translations/ # i18n resources +`-- dist/ # Build output +``` + +## Build and Run Commands + +### Development + +```bash +npm run dev +npm run dev:no-seed +npm run dev:typeorm +npm run dev:typeorm:no-seed +npm run dev:cors +``` + +### Start + +```bash +npm run start +npm run start:seed +npm run start:typeorm +npm run start:typeorm:seed +``` + +### Build + +```bash +npm run build +npm run build:backend +npm run compile:backend +npm run compile:ui +npm run build:assets +npm run build:module +``` + +### Tests + +```bash +npm run test +npm run test:watch +``` + +## Environment Variables + +See `.env.example`. +Important variables include: + +- `OPENAI_API_KEY` +- `ADMIN_LOGIN` +- `ADMIN_PASS` +- `JWT_SECRET` +- `AP_PASSWORD_SALT` +- `ORM` +- `NO_SEED_DATA` +- `ADMINIZER_ENV` + +## Development Conventions + +- Use TypeScript and keep identifiers in English +- Keep comments concise and in English +- Follow existing project architecture and naming style +- Use axios for frontend HTTP requests (CSRF handling is already integrated) + +## Rules for AI Agents in This Repository + +1. Do not create commits unless the user explicitly asks. +2. Write commit messages in English when commits are requested. +3. Reply in Russian in chat communication. +4. Do not start the development server unless the user explicitly asks. +5. After code changes, run `npm run compile:backend` by default. +6. Update documentation only when the user explicitly asks. +7. Do not translate technical artifacts (paths, keys, code identifiers). + +## Completion Checklist + +Before finishing a coding task: + +1. Ensure changed code is consistent with Sequelize-first direction. +2. Run `npm run compile:backend` (unless user requested a different verification flow). +3. Report what was changed and any checks that were not run. diff --git a/Readme.md b/Readme.md index 0cc6ee12..8344d19e 100644 --- a/Readme.md +++ b/Readme.md @@ -40,6 +40,18 @@ Key features: --- +## Architecture: Handler Registry pattern + +Extension points in Adminizer follow the **Handler Registry** pattern — a combination of *Strategy*, *Template Method* and *Registry*: + +- An **abstract handler** (e.g. `AbstractFeedbackHandler`, `AbstractCustomFilter`) declares the contract — abstract methods the user must implement, plus optional template methods built on top of them. +- A **registry** (e.g. `FeedbackHandler`, `CustomFilterHandler`) stores registered handlers by ID and dispatches calls to the matching strategy at runtime. +- Users plug in their own subclasses as runtime extensions — no rebuilds, no core changes. + +This keeps the core framework-agnostic while letting every integration swap in its own behavior. + +--- + > All agents must read the **AGENTS.md** file before proceeding. It contains important guidelines and responsibilities required for proper operation. [Go to the documentation](https://github.com/adminization/adminizer/blob/main/docs/index.md) \ No newline at end of file diff --git a/TODO b/TODO deleted file mode 100644 index 072748fb..00000000 --- a/TODO +++ /dev/null @@ -1,2 +0,0 @@ -Для перевода уведомлений нужно создавать instance i18n в локале юзера, для которого отправляются уведомления. -Для перевода строк уведомлений нужно в БД сохранять массив для обработки по анадлогии с (PHP) print_f('user %s has been deleted by %s', guest, admin) \ No newline at end of file diff --git a/adminizer-module.d.ts b/adminizer-module.d.ts index b125e660..03966f4f 100644 --- a/adminizer-module.d.ts +++ b/adminizer-module.d.ts @@ -12,7 +12,6 @@ */ import type { FC, ReactNode, ComponentProps, RefObject } from 'react'; -import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; // --------------------------------------------------------------------------- // Sonner toast API (subset exposed via window.sonner) @@ -41,25 +40,52 @@ interface SonnerToast { // adminApi — typed HTTP client (mirrors src/assets/js/lib/admin-api.ts) // --------------------------------------------------------------------------- +type QueryParams = Record; + +interface HttpRequestOptions { + headers?: Record; + params?: QueryParams; + signal?: AbortSignal; + responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text'; +} + +interface HttpResponse { + data: T; + status: number; + headers: Record; +} + /** * Typed HTTP client for backend calls. * * Always sends no-cache headers and appends `_ts` query param to bust caches. - * Throws a human-readable error if the session expired and server returned HTML. + * Use `*Json` methods when the endpoint must return JSON; they throw a + * human-readable error if the session expired and server returned HTML. * * @example * const { data } = await adminApi.getJson<{ items: Item[] }>('/admin/api/items'); * await adminApi.postJson('/admin/api/items', { name: 'New item' }); */ interface AdminApi { - /** GET request, returns typed AxiosResponse */ - getJson(url: string, config?: AxiosRequestConfig): Promise>; - /** POST request with JSON body */ - postJson(url: string, data?: unknown, config?: AxiosRequestConfig): Promise>; - /** PUT request with JSON body */ - putJson(url: string, data?: unknown, config?: AxiosRequestConfig): Promise>; - /** DELETE request */ - deleteJson(url: string, data?: unknown, config?: AxiosRequestConfig): Promise>; + get(url: string, options?: HttpRequestOptions): Promise>; + post(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + put(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + patch(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + delete(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + getJson(url: string, options?: HttpRequestOptions): Promise>; + postJson(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + putJson(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + patchJson(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; + deleteJson(url: string, data?: unknown, options?: HttpRequestOptions): Promise>; +} + +interface AxiosCompat { + get(url: string, config?: HttpRequestOptions): Promise>; + post(url: string, data?: unknown, config?: HttpRequestOptions): Promise>; + put(url: string, data?: unknown, config?: HttpRequestOptions): Promise>; + patch(url: string, data?: unknown, config?: HttpRequestOptions): Promise>; + delete(url: string, config?: HttpRequestOptions & { data?: unknown }): Promise>; + isAxiosError(error: unknown): boolean; } // --------------------------------------------------------------------------- @@ -206,8 +232,9 @@ declare global { routePrefix: string; /** - * Typed HTTP client. Use instead of raw `window.axios` for backend calls. - * Adds no-cache headers, cache-buster param, and throws on HTML responses. + * Typed HTTP client. Use instead of legacy `window.axios` for backend calls. + * Adds no-cache headers and cache-buster params. Use `*Json` methods for + * JSON endpoints that should reject HTML responses. * * @example * const { data } = await window.adminApi.getJson<{ rows: Row[] }>(`${window.routePrefix}/api/rows`); @@ -215,9 +242,9 @@ declare global { adminApi: AdminApi; /** - * Raw Axios instance. Prefer `window.adminApi` for JSON API calls. + * Legacy Axios-compatible client. Prefer `window.adminApi`. */ - axios: AxiosInstance; + axios: AxiosCompat; /** * Sonner toast API. diff --git a/controls/wysiwyg/ReactQuill.ts b/controls/wysiwyg/ReactQuill.ts index 2dbec889..327f84c4 100644 --- a/controls/wysiwyg/ReactQuill.ts +++ b/controls/wysiwyg/ReactQuill.ts @@ -23,7 +23,7 @@ export class ReactQuill extends AbstractControls { } getJsPath(): string { - if (process.env.VITE_ENV === 'dev') { + if (process.env.ADMINIZER_ENV === 'dev') { return this.path.jsPath.dev; } else { return this.path.jsPath.production diff --git a/docs/AccessRights/access-rights-tokens.md b/docs/AccessRights/access-rights-tokens.md index ae4b8e4c..5ae699ad 100644 --- a/docs/AccessRights/access-rights-tokens.md +++ b/docs/AccessRights/access-rights-tokens.md @@ -1,5 +1,5 @@ # Access Rights -When sails-adminpanel starts, for every Model it creates 4 access rights tokens: +When Adminizer starts, every configured model gets 4 access rights tokens: - create - read - update @@ -11,18 +11,20 @@ You can also create custom access rights tokens using function `registerToken` o These tokens can be used to give users rights to see information of specific Model, to create new models or edit it. Also, you can use tokens to create access rights to global and inline actions, or to Model tools. -In controllers you should check access rights through `havePermission` method. +In controllers you should check access rights through `hasPermission`. Example: -```javascript -if (sails.config.adminpanel.auth) { - if (!req.session.UserAP) { // check that user is authorized - return res.redirect(`${sails.config.adminpanel.routePrefix}/model/userap/login`); - } else if (!AccessRightsHelper.havePermission(`tokenName`, req.session.UserAP)) { // check permission - return res.sendStatus(403); - } +```ts +if (req.adminizer.config.auth.enable) { + if (!req.user) { + return res.redirect(`${req.adminizer.config.routePrefix}/model/userap/login`); + } + + if (!req.adminizer.accessRightsHelper.hasPermission('tokenName', req.user)) { + return res.sendStatus(403); } +} ``` ### Registering Custom Tokens @@ -43,9 +45,10 @@ adminizer.accessRightsHelper.registerTokens([ Assign the token to a group so members inherit the permission: ```ts -await GroupAP.updateOne({ name: 'managers' }).set({ - tokens: ['reports-export'] -}) +await adminizer.modelHandler + .internal("access-rights") + .get("GroupAP") + .update({ where: { name: 'managers' } }, { tokens: ['reports-export'] }); ``` @@ -64,8 +67,8 @@ will be found, adminpanel will create admin profile with this credentials. If credentials in config will not be found, adminpanel will create admin with login `admin` and numeric password that will be displayed in console. -```javascript -module.exports.adminpanel = { +```ts +const config = { administrator: { login: 'string', password: 'string' diff --git a/docs/AiAssistant.md b/docs/AiAssistant.md index ef8fe38e..d946d36c 100644 --- a/docs/AiAssistant.md +++ b/docs/AiAssistant.md @@ -31,23 +31,21 @@ This ensures that the AI assistant subsystem has zero overhead when disabled. #### Fixture Configuration -In the fixture, the AI assistant is controlled by the `ENABLE_AI_ASSISTANT` environment variable: +In the fixture, the AI assistant is disabled by default in `fixture/adminizerConfig.ts`: -```bash -# Enable AI assistant in fixture -export ENABLE_AI_ASSISTANT=true - -# Disable AI assistant in fixture (default) -export ENABLE_AI_ASSISTANT=false -# or simply omit the variable +```ts +aiAssistant: { + enabled: false, + defaultModel: 'openai-data', + models: ['openai-data', 'dummy'], +} ``` -This allows testing the application both with and without the AI assistant enabled. +Set `enabled: true` locally to test the assistant. The default configuration registers the in-memory `dummy` model that always replies with `Ai-assystant dummy in deveploment`. -The fixture configuration enables the assistant with this dummy model so the chat can be exercised locally without any external -dependencies. +When the assistant is enabled, the built-in `dummy` model lets you exercise the chat locally without any external dependencies. ## Fixture OpenAI Agent @@ -89,7 +87,7 @@ All endpoints require the user to have the access token generated for the model. * `AiAssistantPanel` anchors a full-height chat interface to the right quarter of the viewport as a fixed workspace on desktop, or full-screen on mobile, rendering conversation history and the compose form outside of the main layout. Users can send messages by clicking Send or pressing Ctrl+Enter (Cmd+Enter on Mac). * Conversation history is stored client-side for rendering while the server keeps the authoritative in-memory copy. -**See [Mobile Layout Documentation](./AiAssistant-Mobile.md) for details on responsive behavior.** +On mobile devices, the assistant panel uses the full screen instead of shifting the main layout. ## Extending With New Models diff --git a/docs/Authorization.md b/docs/Authorization.md index e69de29b..54eb1fe8 100644 --- a/docs/Authorization.md +++ b/docs/Authorization.md @@ -0,0 +1,35 @@ +# Authorization + +Adminizer authorization is controlled by the `auth` block in `AdminpanelConfig`. + +```ts +const config = { + routePrefix: "/adminizer", + auth: { + enable: true, + captcha: true, + }, + administrator: { + login: "admin", + password: "change-me", + }, + models: {}, +}; +``` + +When `auth.enable` is `true`, Adminizer registers login, logout, and registration routes under the configured `routePrefix`. + +## Administrator bootstrap + +If there is no administrator account, Adminizer can route users to the initial user setup screen. The fixture provides administrator credentials through `ADMIN_LOGIN` and `ADMIN_PASS`, falling back to local defaults in `fixture/adminizerConfig.ts`. + +## Access gate + +Successful login is not the only check. Users also need the `access-to-adminpanel` token unless they are administrators. Model actions are guarded by generated tokens in the form: + +* `create--model` +* `read--model` +* `update--model` +* `delete--model` + +Custom feature permissions can be registered through `adminizer.accessRightsHelper.registerToken(...)`. diff --git a/docs/BuildingModules.md b/docs/BuildingModules.md index 3ddd2229..0be83d70 100644 --- a/docs/BuildingModules.md +++ b/docs/BuildingModules.md @@ -49,7 +49,7 @@ export default function MyPageModule({ data }: PageModuleProps<{ items: Item[] } const handleAction = async (id: number) => { setLoading(true); try { - await window.axios.post(`${window.routePrefix}/api/my-resource/${id}/action`); + await window.adminApi.postJson(`${window.routePrefix}/api/my-resource/${id}/action`); window.sonner.toast.success('Done'); } catch { window.sonner.toast.error('Failed'); @@ -153,7 +153,7 @@ export class MyEditor extends AbstractControls { getName() { return this.name; } getConfig() { return this.config; } - getJsPath() { return process.env.VITE_ENV === 'dev' ? this.path.jsPath.dev : this.path.jsPath.production; } + getJsPath() { return process.env.ADMINIZER_ENV === 'dev' ? this.path.jsPath.dev : this.path.jsPath.production; } getCssPath() { return this.path.cssPath || undefined; } } ``` @@ -203,6 +203,9 @@ export default defineConfig({ viteExternalsPlugin({ 'react': 'React', 'react-dom': 'ReactDOM', + // Optional, only if your external module imports `axios` directly. + // Adminizer exposes `window.axios` as a legacy compatibility client. + // Prefer `window.adminApi` for new code. 'axios': 'axios', 'sonner': 'sonner', }), @@ -269,7 +272,7 @@ const { Pencil, Trash2, Plus } = window.LucideReact; - Never `import` shadcn, Radix, or Lucide directly — use the globals above. - Wrap your root with `` once if you use `window.sonner.toast`. - Wrap with `` once if you use ``. -- Use `window.adminApi` instead of raw `window.axios` for JSON API calls — it adds no-cache headers and throws a readable error if the session expires. +- Use `window.adminApi` instead of legacy `window.axios` for API calls. Prefer `*Json` methods for JSON endpoints — they add no-cache headers and throw a readable error if the session expires. --- @@ -283,6 +286,7 @@ const { data } = await window.adminApi.getJson<{ rows: Row[] }>( await window.adminApi.postJson(`${window.routePrefix}/api/my-resource`, { name: 'New' }); await window.adminApi.putJson(`${window.routePrefix}/api/my-resource/${id}`, payload); +await window.adminApi.patchJson(`${window.routePrefix}/api/my-resource/${id}`, patch); await window.adminApi.deleteJson(`${window.routePrefix}/api/my-resource/${id}`); ``` diff --git a/docs/Commands.md b/docs/Commands.md index c9722bb9..b6793b86 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -1,48 +1,73 @@ - -## Adminizer Commands - + +## Adminizer Commands + ## Development - **`npm run dev`** Starts the application in development mode using Sequelize with file watching. -- **`npm run dev:waterline`** - Starts the application in development mode using Waterline with file watching. - -- **`npm run watch:backend`** - Watches for changes in backend files and recompiles TypeScript continuously. - -## Build Commands -- **`npm run build:assets`** - Builds frontend assets using Vite. - -- **`npm run build:backend`** - Combines backend copy and compilation steps. - -- **`npm run build`** - Full build process: copies backend, compiles backend, and builds assets. - -## Module-specific Builds (this is just for tests, as an example, you should create your own commands to build modules) -- **`npm run build:module`** - Builds test modules using a custom Vite config. - -- **`npm run build:react-quill`** - Builds the React-Quill WYSIWYG module using a custom Vite config. - -- **`npm run copy:modules`** - Copies modules using `copy-modules.js` script. - -- **`npm run build:assets:modules`** - Builds assets and copies modules. - -## Demo & Seeding -- **`npm run start:seed`** - Starts the application with seed data enabled (using `SEED_DATA=true`). - -- **`npm run demo:build`** - Prepares a demo build: copies backend, compiles backend, builds assets, and copies modules. - -- **`npm run demo`** - Alias for `start:seed` - runs the demo with seeded data. - -## Preview mode -- **`npm start`** - Starts the application using TSX with the Waterline fixture configuration. \ No newline at end of file +- **`npm run dev:typeorm`** + Starts the application in development mode using the experimental TypeORM fixture with file watching. +- **`npm run dev:no-seed`** + Starts the Sequelize fixture in development mode without seed data. +- **`npm run dev:typeorm:no-seed`** + Starts the TypeORM fixture in development mode without seed data. +- **`npm run dev:no-seed-clean`** + Starts the Sequelize fixture without seed data and cleans `.tmp` before startup. +- **`npm run dev:cors`** + Starts the Sequelize fixture with CORS settings for frontend integration testing. + +- **`npm run tsc:watch`** + Watches for changes in backend files and recompiles TypeScript continuously. + +Development mode is detected with `ADMINIZER_ENV=dev`. The bundled `dev` +scripts set it automatically; use the same variable for custom local commands. + +## Build Commands +- **`npm run build:assets`** + Builds frontend assets using Vite. + +- **`npm run build:backend`** + Combines backend copy and compilation steps. + +- **`npm run compile:backend`** + Compiles backend TypeScript and appends `.js` extensions in generated imports. + +- **`npm run compile:ui`** + Compiles the shared UI TypeScript package. + +- **`npm run build`** + Full build process: copies backend, compiles backend, compiles UI, and builds assets. + +## Module-specific Builds (this is just for tests, as an example, you should create your own commands to build modules) +- **`npm run build:module`** + Builds test modules using a custom Vite config. + +- **`npm run build:react-quill`** + Builds the React-Quill WYSIWYG module using a custom Vite config. + +- **`npm run copy:modules`** + Copies modules using `copy-modules.js` script. + +- **`npm run build:assets:modules`** + Builds assets and copies modules. + +## Demo & Seeding +- **`npm run start`** + Starts the Sequelize fixture without seed data. + +- **`npm run start:typeorm`** + Starts the experimental TypeORM fixture without seed data. + +- **`npm run start:seed`** + Starts the Sequelize fixture with seed data enabled. + +- **`npm run start:typeorm:seed`** + Starts the experimental TypeORM fixture with seed data enabled. + +- **`npm run demo:build`** + Prepares a demo build: copies backend, compiles backend, builds assets, and copies modules. + +- **`npm run demo`** + Runs the Sequelize demo with seeded data. + +- **`npm run demo:typeorm`** + Runs the experimental TypeORM demo with seeded data. diff --git a/docs/Configuration/General.md b/docs/Configuration/General.md index 7711c6ab..4884b62e 100644 --- a/docs/Configuration/General.md +++ b/docs/Configuration/General.md @@ -8,7 +8,7 @@ import { AdminizerConfig } from "adminizer"; const config: AdminizerConfig = { routePrefix: "/admin", favicon: "/brand/admin-favicon.ico", - auth: true, + auth: { enable: true }, dashboard: true, models: {}, }; @@ -20,15 +20,21 @@ const config: AdminizerConfig = { |--------|-------------| | `routePrefix` | Base URL for the panel. Defaults to `/admin`. | | `favicon` | Custom favicon URL. Relative values are resolved from `routePrefix`; default is `/files/favicon.png`. | +| `auth` | Authentication settings, for example `{ enable: true, captcha: true }`. | | `linkAssets` | Symlink static assets instead of copying them. | | `identifierField` | Default primary key for models (usually `id`). | -| `showORMtime` | Show `createdAt`/`updatedAt` fields in forms. | +| `showORMtime` | Show `createdAt`/`updatedAt` fields on create/edit pages. | | `models` | Object with model definitions. | | `dashboard` | Enable dashboard widgets. | | `showVersion` | Display Adminizer version in the sidebar. | +| `system.internalModelAccess` | Extend allowlisted internal model access scopes for trusted system modules. | Additional options like `welcome`, `translation` and `administrator` credentials can also be provided. +## Migrations + +Starting with Adminizer 5, built-in migrations are not supported. Projects should manage database schema changes with their own ORM or migration tool. For Sequelize projects, register Adminizer system models and synchronize or migrate them through the host application's database workflow. + **`favicon` examples** ```ts @@ -44,3 +50,29 @@ favicon: "files/my-custom-favicon.png" // -> /admin/files/my-custom-favicon.png // 4) full external URL: favicon: "https://cdn.example.com/admin/favicon.svg" ``` + +## Internal Model Access + +Adminizer system modules can use `modelHandler.internal(scope)` to access selected internal models without `DataAccessor` user filtering. Built-in scopes cover Adminizer's own modules; projects can add custom scopes through `system.internalModelAccess`. + +```ts +const config: AdminizerConfig = { + routePrefix: "/admin", + system: { + internalModelAccess: { + "my-module": ["UserAP", "FilterAP"] + } + } +}; +``` + +Usage: + +```ts +const filters = await adminizer.modelHandler + .internal("my-module") + .get("FilterAP") + .find({ where: { modelName: "Example" } }); +``` + +Use this only for trusted system-level code. Normal user-facing operations should continue to use model methods with `DataAccessor`. diff --git a/docs/Configuration/InlineEditing.md b/docs/Configuration/InlineEditing.md new file mode 100644 index 00000000..fd74ec34 --- /dev/null +++ b/docs/Configuration/InlineEditing.md @@ -0,0 +1,197 @@ +# Inline Editing in List View + +Adminizer supports inline editing of fields directly in the list view. This allows administrators to quickly update values without navigating to the edit form. + +## Overview + +When inline editing is enabled for a field, users can double-click on a cell value in the table to edit it directly. Changes are saved via an API call and the table updates instantly without a full page reload. + +## Enabling Inline Editing + +To enable inline editing for a field, set the `inlineEditable` flag in the field configuration: + +```ts +models: { + Example: { + title: 'Example', + model: 'example', + fields: { + title: { + title: 'Title', + type: 'string', + inlineEditable: true + }, + isActive: { + title: 'Active', + type: 'boolean', + inlineEditable: true + }, + price: { + title: 'Price', + type: 'number', + inlineEditable: true, + inlineValidation: { + min: 0, + max: 99999 + } + } + } + } +} +``` + +## Supported Field Types + +Inline editing is only available for the following field types: + +| Type | Control | +|------|---------| +| `string` | Text input | +| `integer` | Number input | +| `number` | Number input | +| `float` | Number input | +| `email` | Email input | +| `range` | Number input with min/max | +| `boolean` | Checkbox | + +If `inlineEditable: true` is set on an unsupported field type (e.g., `text`, `date`, `json`), the flag is **ignored** and the field remains read-only. + +## Conditions for Editing + +A field is editable in the list view only when **all** of the following conditions are met: + +| Condition | Description | +|-----------|-------------| +| `inlineEditable: true` | The flag must be explicitly set | +| No `displayModifier` | Fields with `displayModifier` are always read-only (priority) | +| Supported field type | The field type must be in the allowed list above | + +## Validation + +You can define validation rules for inline editing using the `inlineValidation` option: + +```ts +fieldName: { + title: 'Field Name', + type: 'string', + inlineEditable: true, + inlineValidation: { + minLength: 3, + maxLength: 100, + pattern: '^[a-zA-Z]+$', + validate: (value) => value.trim().length > 0 || 'Value cannot be empty' + } +} +``` + +### Validation Options + +| Option | Description | Applicable Types | +|--------|-------------|------------------| +| `minLength` | Minimum string length | `string`, `email` | +| `maxLength` | Maximum string length | `string`, `email` | +| `min` | Minimum value | `integer`, `number`, `float`, `range` | +| `max` | Maximum value | `integer`, `number`, `float`, `range` | +| `pattern` | Regex pattern for validation | `string`, `email` | +| `validate` | Custom validation function | All types | + +## User Interaction + +### Text and Number Fields + +1. **Double-click** on the cell value to start editing +2. An input field appears with the current value selected +3. Press **Enter** or click outside to save +4. Press **Escape** to cancel + +### Boolean Fields + +1. **Double-click** on the cell to start editing +2. A checkbox appears +3. Toggle the checkbox to change the value +4. Click outside the checkbox to save +5. Press **Escape** to cancel + +## API Endpoint + +Inline editing uses the following API endpoint: + +``` +PATCH /adminizer/model/:name/inline/:id +``` + +**Request body:** +```json +{ + "field": "fieldName", + "value": "newValue" +} +``` + +**Response:** +```json +{ + "success": true, + "data": { + "id": "record-id", + "field": "fieldName", + "value": "newValue" + } +} +``` + +## Example Configuration + +```ts +import { AdminpanelConfig } from "adminizer"; + +const config: AdminpanelConfig = { + routePrefix: "/admin", + models: { + Product: { + title: 'Products', + model: 'product', + fields: { + name: { + title: 'Name', + type: 'string', + inlineEditable: true, + inlineValidation: { + minLength: 2, + maxLength: 200 + } + }, + price: { + title: 'Price', + type: 'number', + inlineEditable: true, + inlineValidation: { + min: 0, + max: 999999 + } + }, + active: { + title: 'Active', + type: 'boolean', + inlineEditable: true + }, + email: { + title: 'Contact Email', + type: 'email', + inlineEditable: true, + inlineValidation: { + pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$' + } + }, + // This field has displayModifier — inlineEditable will be ignored + displayName: { + title: 'Display Name', + type: 'string', + inlineEditable: true, // ← Ignored due to displayModifier + displayModifier: (v) => v?.toUpperCase() + } + } + } + } +}; +``` diff --git a/docs/Configuration/Localization.md b/docs/Configuration/Localization.md index df7eda43..f5c58b1d 100644 --- a/docs/Configuration/Localization.md +++ b/docs/Configuration/Localization.md @@ -2,20 +2,22 @@ Adminizer supports interface translations using the `translation` block in the configuration. -```js -module.exports.adminpanel = { +```ts +const config = { translation: { locales: ['en', 'de'], - path: 'config/locales', + directory: 'config/locales', defaultLocale: 'en' } }; ``` * `locales` – list of available locales -* `path` – relative path to translation files +* `directory` – relative path to translation files * `defaultLocale` – locale used when none is specified +`path` is still accepted for older configs, but is deprecated. Use `directory` for new projects. + In Inertia mode, interface strings are translated on the server through `req.i18n.__(...)` before props are sent to React components. This includes shared UI props such as sidebar navbar item titles and section labels. ## Shared UI keys @@ -27,7 +29,9 @@ If you localize Adminizer UI through translation dictionaries, make sure the loc * `Visible` * `First`, `Last`, `Previous`, `Next` -These keys are passed via shared `uiMessages` in Inertia mode and are used by menu/user controls and pagination UI. +These keys are passed via shared `i18n.common` in Inertia mode and are used by menu/user controls and pagination UI. + +For page-specific dictionaries, Adminizer uses `i18nPage` props, while reusable/common labels stay in `i18n.common`. ## Locale parity diff --git a/docs/Configuration/Models.md b/docs/Configuration/Models.md index ad751921..260ea82f 100644 --- a/docs/Configuration/Models.md +++ b/docs/Configuration/Models.md @@ -1,15 +1,24 @@ # System Models -Adminizer ships with several built‑in models used for authentication, media management and navigation. These definitions reside in `src/models` and can be automatically registered for **Waterline** and **Sequelize** ORMs by calling `registerSystemModels()` on the corresponding adapter. +Adminizer ships with several built-in models used for authentication, media management, navigation, filters, notifications, and history. These definitions reside in `src/models`. + +Sequelize is the primary supported ORM. Register system models with `SequelizeAdapter.registerSystemModels(sequelize)`. + +TypeORM support is experimental. For the fixture, system entities are loaded with `TypeOrmAdapter.loadSystemEntities()` and passed to the TypeORM `DataSource`. The provided models are: - `UserAP` - `GroupAP` +- `FilterAP` +- `FilterColumnAP` +- `HistoryActionsAP` - `MediaManagerAP` - `MediaManagerAssociationsAP` - `MediaManagerMetaAP` - `NavigationAP` +- `NotificationAP` +- `UserNotificationAP` They can be created and queried like any other models once registered. diff --git a/docs/Controls.md b/docs/Controls.md index aa95ac3a..46035e19 100644 --- a/docs/Controls.md +++ b/docs/Controls.md @@ -245,7 +245,7 @@ export class ReactQuill extends AbstractControls { } getJsPath(): string { - if (process.env.VITE_ENV === 'dev') { + if (process.env.ADMINIZER_ENV === 'dev') { return this.path.jsPath.dev; } else { return this.path.jsPath.production diff --git a/docs/Dashboard.md b/docs/Dashboard.md index bb6d1570..a41e5132 100644 --- a/docs/Dashboard.md +++ b/docs/Dashboard.md @@ -48,18 +48,17 @@ module.exports.adminpanel = { * link [LinkBase] - provide link logic in widget * custom [CustomBase] - mount JS/html widget into dashboard widget -Widgets have magic on loading sails project such as model or service. This function work on -`sails.config.adminpanel.autoloadWidgetsPath: string`. A widget class is created from an abstract base and passed to `WidgetHandler.add`. +Widgets are registered on the Adminizer instance through `adminizer.widgetHandler.add(widget)`. The dashboard config can also define `autoloadWidgetsPath` for project-specific widget loading. > ⚠️ Any new widget type must be added to `WidgetType` inside `/lib/widgets/widgetHandller.ts`. Within this handler you also need to implement the logic for the new type in the `getAll` method. -> ⚠️ You can access widget handler by call method of admin panel `sails.hooks.adminpanel.getWidgetHandler()` +> ⚠️ You can access the widget handler as `adminizer.widgetHandler`. - > ⚠️ You can add a widget by calling `sails.hooks.adminpanel.addDashboardWidget(widget: WidgetType)`. + > ⚠️ You can add a widget by calling `adminizer.widgetHandler.add(widget)`. WidgetBase instance has this props: -- `ID`: string, required +- `id`: string, required - `name`: string, - `description`: string, - `icon`: string | null, default = null @@ -118,11 +117,11 @@ public abstract readonly widgetType: Next Handling Custom Widgets. Add logic to `getAll` method: else if (widget instanceof CustomBase) { - if (AccessRightsHelper.havePermission(`widget-${widget.ID}`, user)) { + if (adminizer.accessRightsHelper.hasPermission(`widget-${widget.id}`, user)) { widgets.push({ - id: `${widget.ID}_${id_key}`, + id: `${widget.id}_${id_key}`, type: widget.widgetType, - api: `${config.routePrefix}/widgets-custom/${widget.ID}`, + api: `${config.routePrefix}/widgets-custom/${widget.id}`, description: widget.description, icon: widget.icon, name: widget.name, @@ -134,7 +133,7 @@ Next Handling Custom Widgets. Add logic to `getAll` method: Then we`ll create Instance of this widget type class CustomOne extends CustomBase { - readonly ID: string = 'site_custom'; + readonly id: string = 'site_custom'; readonly department: string = 'test'; readonly description: string = 'Widget Custom One'; readonly icon: string = 'dog'; @@ -146,13 +145,13 @@ class CustomOne extends CustomBase { And export it as function which create our instances -const setCustomsWidgets = () => { - WidgetHandler.add(new CustomOne()) +const setCustomsWidgets = (adminizer) => { + adminizer.widgetHandler.add(new CustomOne()) } `module.exports = { setCustomsWidgets }` -Then call `setCustomsWidgets` inside `bootstrap.js` +Then call `setCustomsWidgets(adminizer)` after Adminizer is initialized. ### customizable and programmable promotion diff --git a/docs/Export.md b/docs/Export.md new file mode 100644 index 00000000..c481f35e --- /dev/null +++ b/docs/Export.md @@ -0,0 +1,168 @@ +# Export Data + +Adminizer supports exporting filtered data from model list views in three formats: **JSON**, **CSV**, and **XLSX**. + +## Overview + +The export feature allows users to download all records (respecting the currently active filter) from any model list page. Export is available without pagination limits. + +## Supported Formats + +| Format | Description | Use Case | +|--------|-------------|----------| +| **JSON** | Raw JSON array of objects | API integration, data backup, programmatic processing | +| **CSV** | Comma-separated values with UTF-8 BOM | Opening in Excel, Google Sheets, data analysis | +| **XLSX** | Native Excel spreadsheet with auto-sized columns | Business reports, presentations | + +## Backend API + +### Endpoint + +``` +POST /adminizer/model/:modelName/export +``` + +### Request Body + +```json +{ + "format": "json|csv|xlsx", + "filterId": "optional-filter-id" +} +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `format` | string | No | Export format: `json`, `csv`, or `xlsx`. Defaults to `json`. | +| `filterId` | string | No | ID of a saved filter to apply. If omitted, exports all records. Use `temporary` for session-based temporary filters. | +| `selectedFields` | string[] | No | Array of field names to include in export. If omitted, uses filter's column config or all fields. | + +### Response + +- **Success:** File download with appropriate `Content-Type` and `Content-Disposition` headers +- **400:** Invalid format or missing required data +- **401:** Unauthorized (if auth is enabled) +- **403:** Forbidden (insufficient permissions) +- **404:** Model not found +- **500:** Export failed (e.g., xlsx library not installed) + +### Response Headers + +``` +Content-Type: application/json (for JSON) +Content-Type: text/csv; charset=utf-8 (for CSV) +Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (for XLSX) +Content-Disposition: attachment; filename="modelname_export_1234567890.json" +``` + +## Frontend Usage + +The export button appears automatically in the table toolbar when the page loads. + +### How to Export + +1. Navigate to any model list page +2. (Optional) Apply a saved filter to narrow down results +3. Click the **Экспорт** (Export) button in the toolbar +4. Select the desired format: JSON, CSV, or XLSX +5. The file downloads automatically + +### Filter Integration + +- If a **saved filter** is active, only filtered records are exported +- If a **temporary filter** is applied, it is used for the export +- If **no filter** is active, all records are exported + +## Implementation Details + +### Controller: `src/controllers/exportData.ts` + +The export controller: +1. Validates the user has `read-{modelName}-model` permission +2. Loads the saved filter (if `filterId` is provided) +3. Converts datetime conditions from HTML5 format to UTC +4. Applies custom column configuration from filter +5. Converts filter conditions through `QueryBuilder` into internal `QueryCriteria` +6. Executes a query with no pagination (large limit) +7. Selects only real database columns at SQL level; relation/display fields are loaded and formatted after records are fetched +8. Formats data based on field types (relationships, booleans, dates, JSON) +9. Generates the file and sends it as a download + +### Route Registration: `src/system/Router.ts` + +```typescript +import _exportData from "../controllers/exportData"; + +adminizer.app.post(`${baseRoute}/export`, adminizer.policyManager.bindPolicies(policies, _exportData)); +``` + +### Frontend: `src/assets/js/components/list-table/table-toolbar.tsx` + +The export dropdown: +- Uses Adminizer HTTP client (`adminApi`) for export requests and creates a `Blob` for download +- Reads `filterId` from URL to preserve filter state +- Extracts filename from `Content-Disposition` header +- Shows toast notifications for success/error + +## Dependencies + +| Package | Purpose | Required | +|---------|---------|----------| +| `xlsx` | XLSX file generation | Yes (for XLSX export) | + +Install xlsx: +```bash +npm install xlsx +``` + +## CSV Encoding + +CSV files include a **UTF-8 BOM** (`\uFEFF`) prefix to ensure proper character encoding when opened in Microsoft Excel. + +## Data Formatting + +The export applies human-readable transformations: +- **Boolean:** `true` → `Yes`, `false` → `No` +- **Relationships:** Display field (title/name/id) is used +- **Has-Many:** Comma-separated list of display values +- **JSON:** Pretty-printed with indentation (2 spaces) +- **Dates:** ISO 8601 format (`YYYY-MM-DDTHH:mm:ss.sssZ`) +- **Arrays:** Comma-separated values + +## Security + +- Export respects the same **access rights** as the list view +- Only users with `read-{modelName}-model` permission can export +- Filter access control is enforced (private filters only accessible by owner) +- Datetime conditions are validated and converted to prevent injection + +## Extending + +To add a new export format: + +1. Add the format to the `ExportFormat` type in `exportData.ts` +2. Create a new `send{Format}Export` function +3. Add a case to the switch statement in the main controller +4. Add a menu item in `table-toolbar.tsx` + +Example for PDF export: + +```typescript +// In exportData.ts +async function sendPdfExport(res: ResType, data: Record[], modelName: string) { + // Generate PDF... + return res.send(buffer); +} + +// In the switch +case 'pdf': + return sendPdfExport(res, exportData, entity.name); +``` + +```tsx +// In table-toolbar.tsx + handleExport('pdf')}> + PDF + PDF документ + +``` diff --git a/docs/FeedExport.md b/docs/FeedExport.md new file mode 100644 index 00000000..87710483 --- /dev/null +++ b/docs/FeedExport.md @@ -0,0 +1,193 @@ +# Feed Export API + +Публичный API для экспорта данных фильтров в форматах JSON и XML (Atom). Позволяет внешним системам получать отфильтрованные данные по ссылке без авторизации. + +## Обзор + +Фильтры с включённым API-доступом генерируют уникальный `apiKey`, который можно использовать для получения данных в формате: +- **JSON** — для программной интеграции +- **XML (Atom)** — для RSS-подобных фидов (Яндекс.Маркет, новостные агрегаторы и т.д.) + +## Как включить API для фильтра + +1. Откройте список записей нужной модели +2. Нажмите кнопку **«Фильтры»** → выберите существующий фильтр для редактирования +3. Внизу диалога найдите секцию **«API-доступ (фид)»** +4. Включите переключатель +5. Нажмите **«Показать»** для просмотра ссылок +6. Скопируйте URL для JSON или XML + +### Генерация ключа + +- При первом включении API автоматически генерируется `apiKey` (UUID v4) +- Кнопка **«Перегенерировать»** создаёт новый ключ (старая ссылка перестаёт работать) +- Ключ хранится в базе данных в модели `FilterAP` + +## Эндпоинты + +### JSON экспорт + +``` +GET /adminizer/api/feed/{apiKey}.json +``` + +**Ответ:** +```json +{ + "feed": { + "title": "Название фильтра", + "description": "Описание фильтра", + "modelName": "Example", + "generatedAt": "2026-04-06T12:00:00.000Z", + "totalItems": 42, + "apiKey": "550e8400-e29b-41d4-a716-446655440000", + "items": [ + { + "id": 1, + "name": "Запись 1", + "status": "active", + "createdAt": "2026-04-01T10:00:00.000Z" + } + ] + } +} +``` + +### XML (Atom) экспорт + +``` +GET /adminizer/api/feed/{apiKey}.xml +``` + +**Ответ:** +```xml + + + /adminizer/api/feed/550e8400-e29b-41d4-a716-446655440000 + Название фильтра + Описание фильтра + 2026-04-06T12:00:00.000Z + + Adminizer + + 1 + Запись 1 + 2026-04-01T10:00:00.000Z + Запись 1 + + + id1 + nameЗапись 1 + + ]]> + + {"id":1,"name":"Запись 1"} + + +``` + +## Примеры использования + +### curl + +```bash +# JSON +curl https://example.com/adminizer/api/feed/YOUR_API_KEY.json + +# XML +curl https://example.com/adminizer/api/feed/YOUR_API_KEY.xml +``` + +### JavaScript / Fetch + +```javascript +const response = await fetch('/adminizer/api/feed/YOUR_API_KEY.json'); +const data = await response.json(); +console.log(data.feed.items); +``` + +### Python + +```python +import requests + +url = "https://example.com/adminizer/api/feed/YOUR_API_KEY.json" +response = requests.get(url) +data = response.json() + +for item in data['feed']['items']: + print(item['name']) +``` + +### RSS-агрегатор + +Добавьте ссылку `https://example.com/adminizer/api/feed/YOUR_API_KEY.xml` в ваш RSS-ридер (Feedly, Inoreader и т.д.) + +## Безопасность + +- Для доступа нужны два ключа: `apiKey` фильтра и `userKey` пользователя (`UserAP.apiKey`) +- `apiKey` должен принадлежать приватному фильтру с включенным API-доступом +- `userKey` проверяется перед генерацией фида +- Глобальная авторизация должна быть включена (`auth.enable`) +- При компрометации ключа фильтра — перегенерируйте `apiKey` в UI фильтра +- При компрометации пользовательского ключа — перегенерируйте `userKey` + +## Ошибки + +| Код | Описание | +|-----|----------| +| `400` | Неверный формат. Поддерживаются только `json` и `xml` | +| `401` | Не передан `userKey` | +| `403` | Авторизация выключена или `userKey` неверный | +| `404` | Фильтр не найден или API-доступ отключён | +| `500` | Внутренняя ошибка сервера | + +**Пример ошибки:** +```json +{ + "error": "Filter not found or API access is disabled", + "message": "Check your apiKey or ensure API access is enabled for this filter" +} +``` + +## Архитектура + +### Файлы + +| Файл | Описание | +|------|----------| +| `src/services/FeedService.ts` | Сервис генерации фидов (JSON + XML) | +| `src/controllers/feed.ts` | HTTP-контроллер | +| `src/system/Router.ts` | Регистрация роутов | +| `src/controllers/filter-fields/savedFilters.ts` | Генерация apiKey при сохранении | +| `src/assets/js/components/list-table/filter-panel-save-dialog.tsx` | UI управления API | + +### Формат данных + +Данные форматируются так же, как в обычном экспорте (`exportData.ts`): +- Связи — по title field +- Даты — ISO 8601 +- Булевы — "Yes"/"No" +- Массивы — через запятую +- JSON — сериализованная строка + +Фильтры выполняются тем же путем, что и экспорт списка: условия фильтра преобразуются через `QueryBuilder` во внутренний `QueryCriteria`, а ORM-адаптер переводит criteria в свой формат запроса. + +## Кастомизация + +### Изменить структуру JSON + +Отредактируйте метод `generateJsonFeed()` в `FeedService.ts`: + +```typescript +async generateJsonFeed(filter: FilterAP): Promise { + const { records, fields, modelName } = await this.fetchFilterData(filter); + // Ваша кастомная логика + return { /* ваша структура */ }; +} +``` + +### Изменить XML формат + +Отредактируйте метод `buildAtomXml()` в `FeedService.ts`. diff --git a/docs/Filters.md b/docs/Filters.md new file mode 100644 index 00000000..825ae707 --- /dev/null +++ b/docs/Filters.md @@ -0,0 +1,327 @@ +# Filters in Adminizer + +This document describes the current filter implementation in the codebase (backend + frontend). + +## Overview + +The filter subsystem allows users to: + +- build and apply temporary filters; +- save personal/shared filters with metadata; +- store per-filter column order; +- expose private filters as JSON/XML feeds (with API keys); +- use relation and custom conditions through Adminizer's internal query language. + +Main files: + +- `src/models/FilterAP.ts` +- `src/models/FilterColumnAP.ts` +- `src/lib/filters/FilterService.ts` +- `src/lib/query-builder/QueryBuilder.ts` +- `src/interfaces/queryCriteria.ts` +- `src/controllers/filter-fields/*` +- `src/assets/js/components/list-table/filter-panel.tsx` + +## Data Models + +### `FilterAP` + +| Field | Type | Notes | +|---|---|---| +| `id` | `string` | UUID | +| `name` | `string` | Required | +| `description` | `string?` | Optional | +| `modelName` | `string` | Required | +| `conditions` | `FilterCondition[]` | JSON array | +| `sortField` | `string?` | Optional | +| `sortDirection` | `'ASC' \| 'DESC'?` | Optional | +| `visibility` | `'private' \| 'public' \| 'groups' \| 'system'` | Access mode | +| `ownerId` | `number` | Owner user id | +| `groupIds` | `number[]?` | Used when visibility is `groups` | +| `apiEnabled` | `boolean` | Feed enabled flag | +| `apiKey` | `string?` | Filter API key for feeds | +| `icon` | `string?` | UI icon | +| `color` | `string?` | UI color | +| `version` | `number` | Filter format version (`1`) | +| `createdAt` / `updatedAt` | `Date` | Auto timestamps | + +### `FilterColumnAP` + +`FilterColumnAP` stores one row per selected column: + +| Field | Type | Notes | +|---|---|---| +| `id` | `number` | Auto-increment | +| `filter` | `string \| FilterAP` | Relation to `FilterAP` | +| `fieldName` | `string` | Model field id | +| `order` | `number` | Display order | + +### `FilterCondition` + +```ts +interface FilterCondition { + id: string; + field?: string; + operator?: FilterOperator; + value?: any; + + logic?: 'AND' | 'OR' | 'NOT'; + children?: FilterCondition[]; + + relation?: string; + relationField?: string; + + customHandler?: string; + customHandlerName?: string; + customHandlerParams?: any; + + rawSQL?: string; // legacy shape, not executed by QueryCriteria + rawSQLParams?: any[]; +} +``` + +Usage map (current code): + +| Field | Used in code | Notes | +|---|---|---| +| `id` | yes | Condition identity in UI/backend validation | +| `field` | yes | Standard field filtering path | +| `operator` | yes | Required for query mapping and validation | +| `value` | yes | Operator payload | +| `logic` | yes | Group logic (`AND`/`OR`/`NOT`) in backend/query builder | +| `children` | yes | Nested group conditions in backend/query builder | +| `relation` | yes | Relation filter path, converted to adapter-neutral criteria | +| `relationField` | yes | Related field in relation filter | +| `customHandler` | yes | Custom handler ID for custom filter execution | +| `customHandlerName` | yes | UI display label (optional, fallbacks exist) | +| `customHandlerParams` | yes | Optional params passed to custom handler execution | +| `rawSQL` | legacy | Not executed by `QueryCriteria`; use a custom handler that returns `criteria` | +| `rawSQLParams` | legacy | Kept for compatibility with older condition payloads | + +`FilterCondition` is a UI/saved-filter format. Runtime execution converts it into `QueryCriteria`; ORM adapters do not receive `FilterCondition` directly. + +## Operators + +`FilterOperator` currently includes: + +- `eq`, `neq` +- `gt`, `gte`, `lt`, `lte` +- `like`, `ilike`, `startsWith`, `endsWith` +- `in`, `notIn` +- `between` +- `today`, `month`, `year`, `monthBetween`, `yearBetween` +- `isNull`, `isNotNull` +- `regex` +- `custom` + +Type-specific availability is enforced in `ConditionValidator` and `QueryBuilder`. + +## Query Execution + +Filters are not passed to ORM adapters directly. The execution path is: + +```text +FilterCondition[] + list/search params + -> QueryBuilder + -> QueryCriteria + -> ORM adapter +``` + +`QueryCriteria` is documented in [Internal Queries](InternalQueries.md). It contains only adapter-neutral fields such as `where`, `select`, `populate`, `sort`, `limit`, and `skip`. UI concepts such as `filters`, `globalSearch`, and visible table columns stay outside the internal query language. + +### Important UI note + +The current filter panel supports a flat condition list (combined with `AND`). +Nested `AND/OR/NOT` groups are supported by backend condition format and query builder, but the standard UI does not provide a group editor. + +### Relation filter note + +- Relation conditions are converted to adapter-neutral relation criteria. +- In the current UI and validator flow for relation conditions, allowed operators are `eq` and `neq`. + +## Visibility and Permissions + +Visibility types: + +- `private` - owner + admin +- `groups` - users whose groups intersect with `groupIds` + admin +- `public` - all users with access to the model +- `system` - visible to all users with model access; excluded from saved filter list by default + +Edit/delete rules: + +- admins can edit/delete any filter; +- non-admin users can edit/delete only their own filters. + +Creation rules in `POST /filter`: + +- `public` is allowed only for admins; +- `groups` is allowed for admins or users with `manage-group-filter-visibility` permission token; +- otherwise visibility is forced to `private`. + +When editing someone else's filter, visibility is preserved. + +## Backend API + +Routes are registered under `/model/:entityName/...` patterns. + +### Filter metadata and fields + +- `GET /adminizer/model/:model/filter-fields` + - Returns available fields for filter UI. + - Applies model filter config: `models..filters` (or legacy `modelFilters.`). + - Applies `excludeFromFilters`. + - Always excludes `id`, `ownerId`, `updatedAt`. + - Relation fields are returned only for Sequelize. + - Custom filter metadata is returned for Sequelize fields with `customFilter.handlerId`. + +### Saved filters + +- `GET /adminizer/model/:model/saved-filters` + - Returns accessible filters with: + - owner info (`ownerInfo`) + - computed result count (`resultCount`) + +- `POST /adminizer/model/:model/filter` + - Creates or updates a saved filter. + - Update mode uses `filterId` in body. + - Optional: `columns`, `visibility`, `groupIds`, `sortField`, `sortDirection`, `icon`, `color`. + - For private owner filters, supports `apiEnabled` and `regenerateApiKey`. + - If same-name filter already exists for the same owner and `filterId` is not provided, returns `409` with `requiresConfirmation: true`. + +- `DELETE /adminizer/model/:model/filter/:id` + - Returns `204` for successful deletion. + - Also returns `204` for "not found or access denied" to avoid ownership enumeration. + +### Temporary filters + +- `POST /adminizer/model/:model/filter/apply` + - Stores temporary filter in server session as `req.session.temporaryFilters[modelName]`. + - Returns `filterId: "temporary"`. + +- `GET /adminizer/model/:model/filter/temporary` + - Reads temporary filter from server session. + +Frontend additionally stores temporary filter meta in `sessionStorage` for local UI state; backend source of truth is server session. + +### Columns + +- `GET /adminizer/model/:model/columns?filterId=` + - Returns `availableColumns` and current `filterColumns`. + +- `POST /adminizer/model/:model/filter/:filterId/columns` + - Replaces column rows for the filter. + +### Groups + +- `GET /adminizer/groups` + - Returns group list for group visibility selector. + - Protected by `manage-group-filter-visibility` permission token. + +### Cross-model user filter list + +- `GET /adminizer/api/all-user-filters` + - Returns all filters visible to the current user across models. + - Supports query params: `search`, `modelName`, `offset`, `limit`. + +## Public Feed API + +Feed routes: + +- `GET /adminizer/api/feed/:apiKey` +- `GET /adminizer/api/feed/:apiKey.:format` (`json` or `xml`) + +Requirements: + +- filter must be `visibility: 'private'`; +- filter must have `apiEnabled: true`; +- caller must provide `userKey` query parameter; +- auth must be enabled globally (`config.auth.enable`); +- `userKey` must match an existing user API key (`UserAP.apiKey`). + +Helper endpoints for user API keys: + +- `GET /adminizer/api/user-key` +- `POST /adminizer/api/user-key/regenerate` + +## Custom Filter Handlers + +Custom handler runtime: + +- registry class: `CustomFilterHandler` +- handler base class: `AbstractCustomFilter` +- handler id format: `ModelName.fieldName` + +Register handlers programmatically: + +```ts +adminizer.customFilterHandler.add(new MyFilterHandler(), { force: true }); +``` + +Attach handler to model field: + +```ts +fields: { + myJson: { + type: 'jsoneditor', + customFilter: { + handlerId: 'Example.myJson' + } + } +} +``` + +Notes: + +- custom filter metadata is exposed by `/filter-fields` only for Sequelize fields; +- handler `inputConfig` supports up to 3 inputs; +- in UI, custom condition is serialized with `operator: 'custom'` and `customHandler`. + +## Validation and Security Limits + +Execution-level limits (`FILTER_SECURITY_LIMITS` in `QueryBuilder`): + +| Limit | Value | +|---|---| +| Max nesting depth | 10 | +| Max conditions per group | 100 | +| Max total conditions | 500 | +| Max `IN` values | 1000 | +| Max string length | 10000 | + +`ConditionValidator` provides extra condition validation helpers (field/type/operator/value checks and legacy raw SQL pattern checks), but it is a utility class and is not the only enforcement layer. Query execution still validates key constraints in `QueryBuilder`. + +## Model Configuration + +Global switch: + +```ts +filters: { + enabled: true +} +``` + +Per-model filter configuration: + +```ts +models: { + Example: { + model: 'Example', + title: 'Example', + filters: { + enabled: true, + excludeFromFilters: ['createdAt', 'updatedAt'] + } + } +} +``` + +Legacy fallback is still supported: + +```ts +modelFilters: { + Example: { + excludeFromFilters: ['createdAt'] + } +} +``` diff --git a/docs/Forms.md b/docs/Forms.md index 58060afe..1954af72 100644 --- a/docs/Forms.md +++ b/docs/Forms.md @@ -1,60 +1,5 @@ # Forms -You can add forms when you have no need to create a Model. For example, for -single block on page that will not be repeated. Or like a link with model settings. +Legacy configuration-driven Forms are no longer used starting with Adminizer 5. -You can add forms directly to adminpanel configuration or put them in files with -`.json` extension which should be named as form slug. Put your forms in your -directory and write down path to it in `path` field. - -```javascript -module.exports.adminpanel = { - forms: { - path: 'forms', - data: { - formSlug: { - field1: { - title: "Field1", - type: "string", - value: "Some string", - required: true, - tooltip: 'tooltip for field1', - description: "some description" - } - } - } - }, -} -``` - -- path - relative path to your forms from project directory -- data - object with forms which are named by unique slug - -You can use forms by adding adminpanel special links like `tools`, `additionaLinks` -in navbar, `global` or `inline` actions. To do that read [Links](Links.md) - -## How to create a form - -Forms is `.json` files that contains fields with `BaseFieldConfig` options. -`title` and `type` are required. - -```json -{ - "label": { - "title": "Label", - "type": "string", - "value": "From example from file", - "required": true, - "tooltip": "tooltip for label", - "description": "some description" - }, - "teaser": { - "title": "Тизер", - "type": "text", - "required": true, - "tooltip": "tooltip for teaser", - "description": "some description", - "value": "" - } -} -``` +Use model create/edit pages and model configuration instead. diff --git a/docs/InertiaAdapter.md b/docs/InertiaAdapter.md index 83c13166..32cc4a74 100644 --- a/docs/InertiaAdapter.md +++ b/docs/InertiaAdapter.md @@ -161,8 +161,9 @@ req.Inertia.shareProps({ ``` `menuHelper.getMenuItems(req)` returns sidebar items already localized on the server (titles, sections, and nested action titles are translated via `req.i18n.__(...)` before sending Inertia props). -You can also share a common dictionary (for example `uiMessages`) via `shareProps` and consume it in React components for reusable interface labels. -For dashboard widgets, shared `uiMessages` can include keys like `Show`, `Hide`, `On`, `Off`, and `No widgets found`. +You can also share a common dictionary via `i18n.common` in `shareProps` and consume it in React components for reusable interface labels. +For dashboard widgets, common keys can include labels like `Show`, `Hide`, `On`, `Off`, and `No widgets found`. +Page-specific dictionaries should be passed via `i18nPage`. **See the implementation here** `src/system/bindInertia.ts` ### Usage in controllers diff --git a/docs/Install.md b/docs/Install.md index 432d1b4c..199c25de 100644 --- a/docs/Install.md +++ b/docs/Install.md @@ -7,7 +7,7 @@ To launch **Adminizer**, you need to follow a few simple steps: ## 1. Create an Adminizer instance You must create an instance of the `Adminizer` class. -When creating it, you should pass an array of adapters — adapters are responsible for connecting Adminizer to your data sources (e.g., SequelizeAdapter or WaterlineAdapter). +When creating it, you should pass an array of adapters — adapters are responsible for connecting Adminizer to your data sources. Sequelize is the primary supported adapter. TypeORM support exists, but is experimental. --- @@ -15,10 +15,10 @@ When creating it, you should pass an array of adapters — adapters are responsi Each adapter acts as a bridge between Adminizer and your models. You need to prepare and configure at least one adapter: -- For **Sequelize**, create a database connection and register your models. -- For **Waterline**, create the ORM, register models, and initialize it with configuration. -- Or _use any other ORM_ by implementing a custom adapter that extends AbstractAdapter. -Adminizer is designed to be flexible and can be integrated with any ORM or data source. +- For **Sequelize**, create a database connection and register your models. +- For **TypeORM**, use the experimental `TypeOrmAdapter` only after testing it against your project models. +- Or _use any other ORM_ by implementing a custom adapter that extends AbstractAdapter. +Adminizer is designed to be flexible and can be integrated with any ORM or data source. --- ## 3. Initialize Adminizer @@ -73,10 +73,11 @@ If you encounter difficulties launching **Adminizer**, you can use the ready-made **fixture** (template project) available in the repository folder [`fixture`](https://github.com/adminization/adminizer/tree/main/fixture). This project already includes: -- a fully configured Adminizer setup, -- working Waterline models, -- example seed data, -- and a minimal configuration required to start. +- a fully configured Adminizer setup, +- working Sequelize models, +- experimental TypeORM models, +- example seed data, +- and a minimal configuration required to start. It serves as a practical starting point to help you quickly understand how Adminizer, models, adapters, and configuration fit together. @@ -96,17 +97,17 @@ After running these commands, you will have a fully working Adminizer instance r Example: ```typescript -import { Adminizer } from "@adminization/adminizer"; -import { SequelizeAdapter } from "@adminization/adminizer/v4/model/adapter/sequelize"; -import { Sequelize, DataTypes } from "sequelize"; -import http from "http"; - -async function start() { - // 1. Initialize Sequelize - const sequelize = new Sequelize('sqlite::memory:', { logging: false }); - - // 2. Define a model - const Example = sequelize.define('example', { +import { Adminizer, SequelizeAdapter } from "adminizer"; +import { Sequelize, DataTypes } from "sequelize"; +import express from "express"; + +async function start() { + // 1. Initialize Sequelize + const sequelize = new Sequelize('sqlite::memory:', { logging: false }); + await SequelizeAdapter.registerSystemModels(sequelize); + + // 2. Define a model + const Example = sequelize.define('example', { id: { type: DataTypes.INTEGER, autoIncrement: true, @@ -125,42 +126,44 @@ async function start() { }); // 3. Synchronize the database (this will create the table) - await sequelize.sync({ force: true }); - - // 4. Create an adapter - const sequelizeAdapter = new SequelizeAdapter({ - sequelize: sequelize, - models: { example: Example }, - }); - - // 5. Create Adminizer instance - const adminizer = new Adminizer([sequelizeAdapter]); - - await adminizer.init({ - routePrefix: "/adminizer", - projectName: "Adminizer with Sequelize", - dbConnection: null, - auth: false, - buildAssets: true, - }); - - // 6. HTTP server - const mainApp = http.createServer((req, res) => { - if (req.url.startsWith("/adminizer")) { - adminizer.app(req, res, (err) => { - if (err) { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Internal Server Error'); - } - }); - } else { - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end('

Welcome

Go to Adminizer

'); - } - }); - - mainApp.listen(3000, () => { - console.log('Server started: http://localhost:3000/adminizer'); + await sequelize.sync({ force: true }); + + // 4. Create an adapter + const sequelizeAdapter = new SequelizeAdapter(sequelize); + + // 5. Create Adminizer instance + const adminizer = new Adminizer([sequelizeAdapter]); + + await adminizer.init({ + routePrefix: "/adminizer", + auth: { enable: false }, + models: { + Example: { + title: "Examples", + model: "example", + fields: { + id: { title: "ID", type: "integer" }, + title: { title: "Title", type: "string" }, + description: { title: "Description", type: "text" }, + }, + list: true, + add: true, + edit: true, + view: true, + remove: true, + }, + }, + }); + + // 6. HTTP server + const mainApp = express(); + mainApp.use(adminizer.getMiddleware()); + mainApp.get("/", (_req, res) => { + res.type("html").send('

Welcome

Go to Adminizer

'); + }); + + mainApp.listen(3000, () => { + console.log('Server started: http://localhost:3000/adminizer'); }); } diff --git a/docs/InternalQueries.md b/docs/InternalQueries.md new file mode 100644 index 00000000..22441b34 --- /dev/null +++ b/docs/InternalQueries.md @@ -0,0 +1,193 @@ +# Internal Queries + +Adminizer uses an internal query language named `QueryCriteria` between higher-level services and ORM adapters. The goal is to keep Adminizer code independent from a specific ORM query syntax. UI filters, global search, and custom filter state are converted before execution; they are not part of `QueryCriteria`. + +## QueryCriteria + +```ts +interface QueryCriteria { + where?: CriteriaWhere; + select?: string[] | Record; + populate?: Record; + sort?: string | Record; + limit?: number; + skip?: number; +} +``` + +Basic example: + +```ts +await model.find({ + where: { + parent: null, + mimeType: { contains: "image" }, + group: "default" + }, + sort: "createdAt DESC", + limit: 20, + skip: 0, + select: ["id", "filename", "url"] +}); +``` + +## Operators + +Field values can be primitives, arrays, or operator objects. + +```ts +await model.find({ + where: { + status: "published", + title: { contains: "release" }, + createdAt: { between: [fromDate, toDate] }, + id: { in: [1, 2, 3] }, + deletedAt: { isNull: true } + } +}); +``` + +Supported operators: + +| Operator | Meaning | +|---|---| +| `eq` | Equals | +| `ne` | Not equals | +| `gt`, `gte`, `lt`, `lte` | Numeric/date comparisons | +| `contains` | String contains | +| `startsWith`, `endsWith` | String prefix/suffix | +| `in`, `notIn` | Value is in/not in array | +| `between` | Inclusive range | +| `isNull`, `isNotNull` | Null checks | +| `regex` | Adapter-supported regular expression | +| `jsonContains` | Adapter-supported JSON containment | + +Arrays are treated as `in`: + +```ts +await model.find({ where: { id: [1, 2, 3] } }); +``` + +## Boolean Groups + +`and`, `or`, and `not` are adapter-neutral boolean groups. + +```ts +await model.find({ + where: { + and: [ + { status: "published" }, + { + or: [ + { title: { contains: "Adminizer" } }, + { description: { contains: "Adminizer" } } + ] + }, + { not: { archived: true } } + ] + } +}); +``` + +## Populate + +`populate` describes relations to load. A relation can be `true` or another `QueryCriteria`. + +```ts +await mediaModel.find({ + where: { parent: null }, + populate: { + variants: { + sort: "createdAt DESC", + select: ["id", "url", "mimeType"] + }, + meta: { + where: { isPublic: true } + } + } +}); +``` + +Nested `populate` is allowed: + +```ts +await notificationModel.find({ + populate: { + userNotifications: { + populate: { + userId: true + } + } + } +}); +``` + +## QueryBuilder Boundary + +`QueryBuilder` accepts UI-oriented parameters such as `filters`, `globalSearch`, and `fields`, then converts them into `QueryCriteria`. + +This separation is intentional: + +- `FilterCondition[]` belongs to the filter UI and saved filter models. +- `globalSearch` belongs to list/table UX. +- `fields` is a display/export selection. +- ORM adapters receive only `QueryCriteria`. + +Raw SQL and in-memory custom filter results are not part of `QueryCriteria`. Custom filters that participate in database queries should return adapter-neutral `criteria`. + +## Internal Model Access + +System modules should access internal models through `ModelHandler`: + +```ts +const user = await adminizer.modelHandler + .internal("widgets") + .get("UserAP") + .findOne({ where: { login: "admin" } }); +``` + +This path is intended for trusted Adminizer subsystems that must bypass `DataAccessor` user filtering. It replaces direct calls to protected adapter methods such as: + +```ts +// Do not use in application/module code. +adminizer.modelHandler.model.get("UserAP")["_findOne"](...) +``` + +## Allowlist Scopes + +Each internal access scope has an allowlist of models. Built-in scopes include: + +| Scope | Purpose | +|---|---| +| `auth` | Login, registration, init user | +| `users` | Built-in user/group controllers | +| `filters` | Saved filters and filter columns | +| `media-manager` | Media manager system models | +| `history` | History actions | +| `notifications` | Notification models | +| `navigation` | Navigation storage | +| `widgets` | Dashboard widgets | +| `feed` | Feed export | + +Projects can extend the allowlist with `system.internalModelAccess`: + +```ts +const config = { + system: { + internalModelAccess: { + "my-module": ["UserAP", "FilterAP"] + } + } +}; +``` + +Then use the custom scope: + +```ts +const filters = await adminizer.modelHandler + .internal("my-module") + .get("FilterAP") + .find({ where: { modelName: "Example" } }); +``` + +Use `DataAccessor` for normal user-facing model operations. Use `internal(...)` only for system-level Adminizer modules or trusted integrations. diff --git a/docs/Navigation.md b/docs/Navigation.md index 425cb770..fb86d656 100644 --- a/docs/Navigation.md +++ b/docs/Navigation.md @@ -123,7 +123,7 @@ The editor provides: ## Database -The navigation tree is stored in the `navigationap` table, created by migration `20240804191001-added-navigationap.js`. +The navigation tree is stored in the `navigationap` table. Starting with Adminizer 5, built-in migrations are not supported; create or update this table through your project's own database workflow. | column | type | description | |-------------|--------|------------------------------------------------| @@ -162,11 +162,17 @@ Read the `navigationap` record for the section you need. The `tree` field is rea // index.ts mainApp.get('/api/navigation', async (req, res) => { try { - const header = await adminizer.modelHandler.model - .get('navigationap')['_findOne']({ label: 'header' }); + const navigationModel = adminizer.modelHandler + .internal('navigation') + .get('NavigationAP'); - const footer = await adminizer.modelHandler.model - .get('navigationap')['_findOne']({ label: 'footer' }); + const header = await navigationModel.findOne({ + where: { label: 'header' } + }); + + const footer = await navigationModel.findOne({ + where: { label: 'footer' } + }); res.json({ header: header?.tree ?? [], diff --git a/docs/Notifications.md b/docs/Notifications.md index 354d9bb7..4dc5709e 100644 --- a/docs/Notifications.md +++ b/docs/Notifications.md @@ -65,14 +65,21 @@ The system supports various notification types through separate services. Each t The system provides seamless user interaction by delivering important information in real time while maintaining history for future reference. -## System Initialization +## Configuration -The notification system is initialized when the application starts through the `bindNotifications()` function, which: +Notifications are disabled by default. Enable them in `AdminpanelConfig`: -1. **Creates a handler** - central coordinator for all notifications -2. **Registers services** - connects various notification types: - - `GeneralNotificationService` - general notifications - - `SystemNotificationService` - system events and notifications +```ts +notifications: { + enabled: true, + enableGeneral: true, + initTab: 'general', +} +``` + +The notification system is initialized through `bindNotifications()`. It creates a `NotificationHandler` and registers `GeneralNotificationService` when `notifications.enableGeneral` is enabled. + +`SystemNotificationService` exists in the codebase, but it is not registered by default. ## Integration with Adminizer @@ -93,20 +100,7 @@ await adminizer.sendNotification({ }); ``` -#### 2. System Events -```typescript -// General system event -await adminizer.logSystemEvent( - 'System Update', - 'The system has been successfully updated', - { version: '2.0.0' } -); - -// CRUD operation events -await adminizer.logSystemCreatedEvent('New User', 'User John Doe created'); -await adminizer.logSystemUpdatedEvent('Profile Update', 'User profile updated'); -await adminizer.logSystemDeletedEvent('Record Deletion', 'Record deleted from database'); -``` +For custom notification classes, create a service that extends `AbstractNotificationService` and register it with `adminizer.notificationHandler.registerService(...)` after notifications are initialized. ## Default Notification Service Types @@ -116,8 +110,8 @@ await adminizer.logSystemDeletedEvent('Record Deletion', 'Record deleted from da ### SystemNotificationService - **Purpose**: System events and automatic notifications -- **Usage**: System events, audit trails, action logging +- **Status**: Available in code, but not registered by default ## Access Rights -The system automatically registers access rights for each notification type through `AccessRightsHelper`. Users can only receive notifications for which they have permissions. \ No newline at end of file +The system automatically registers access rights for each notification type through `AccessRightsHelper`. Users can only receive notifications for which they have permissions. diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 3c0afa78..392ada70 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -1,4 +1,4 @@ -### 🔧 Issue with Model Name Casing in Config (Waterline, Sequelize, TypeORM) +### Issue with Model Name Casing in Config (Sequelize, TypeORM) **Description:** @@ -19,12 +19,12 @@ models: { } ``` -The configuration system looks up the model config by exact key match. If the key casing doesn't match what the ERM uses internally, the config is not found, and an error is thrown. +The configuration system looks up the model config by exact key match. If the key casing doesn't match what the ORM adapter uses internally, the config is not found, and an error is thrown. **Temporary Rule:** * Use **lowercase model names** as keys in the `models` config section. -* Match the model name exactly as it is referenced internally by your ERM (Waterline, Sequelize, TypeORM). +* Match the model name exactly as it is referenced internally by your ORM adapter (Sequelize or TypeORM). **Planned Improvements:** diff --git a/docs/UI-guideline.md b/docs/UI-guideline.md new file mode 100644 index 00000000..b75271ab --- /dev/null +++ b/docs/UI-guideline.md @@ -0,0 +1,892 @@ +# Practical Typography for Dense Web Interfaces — Tailwind Edition + +This guide is a Tailwind-ready version of the dense UI typography rules for admin panels, CRM screens, monitoring dashboards, settings pages, tables, and configuration-heavy interfaces. + +The goal is simple: keep the interface compact without making it feel glued together. + +Dense UI does **not** mean tiny UI. Dense UI means clear hierarchy, predictable spacing, visible labels, readable controls, and stable rhythm. + +--- + +## 1. Team summary + +For admin panels and data-heavy screens, use **infrastructure typography**, not expressive brand typography. + +Recommended baseline: + +| Role | Size / line-height | Tailwind class | +|---|---:|---| +| Helper, caption, meta | 12 / 16 | `text-caption` | +| Main UI text, labels, inputs, table cells | 14 / 20 | `text-ui` | +| Long descriptions | 16 / 24 | `text-body` | +| Section title | 16 / 24 or 20 / 28 | `text-body font-semibold` or `text-title` | +| Page title | 20 / 28 or 24 / 32 | `text-title` or `text-page-title` | + +Spacing baseline: + +| Relationship | Recommended spacing | Tailwind class | +|---|---:|---| +| Label → control | 4–8px | `gap-micro` / `gap-tight` | +| Control → helper / error | 4–8px | `gap-micro` / `gap-tight` | +| Field → field | 12–16px | `gap-field` / `gap-group` | +| Subsection → subsection | 16–24px | `gap-group` / `gap-section` | +| Section → section | 24–32px | `gap-section` / `gap-page` | +| Card padding | 16–24px | `p-group` / `p-section` | + +The most important rule: + +> **Use parent stack gaps to separate large blocks. Do not rely on borders alone.** + +--- + +## 2. Why dense interfaces become glued together + +A dense interface usually breaks because the relationships between elements are unclear: + +- A label looks the same as a value. +- Helper text is closer to the next field than to its own field. +- Section cards touch each other with no outer gap. +- Borders are used as the only separator. +- Inner cards and outer cards have the same border strength. +- Everything uses `text-xs` or low-contrast gray. +- Forms use too many columns before they establish a clear vertical rhythm. +- Long descriptions are placed directly between controls. + +In Tailwind, this often starts with code like this: + +```html +
+ +
+
+ +
+``` + +This looks harmless, but the blocks touch. The bottom border of the first card and the top border of the next card visually merge into one continuous block. + +Use a parent stack instead: + +```html +
+
+ +
+ +
+ +
+
+``` + +The parent owns the vertical rhythm. Cards should not decide their own outside spacing. + +--- + +## 3. Specific fix for the “glued blocks” problem + +In the screenshot, the blocks are technically separated by borders, but they still feel glued because the large sections are stacked with almost no external breathing room. The eye reads them as one giant bordered surface. + +Fix it with three layers of spacing: + +### Layer 1 — Page blocks + +Use this for major settings groups such as: + +- Firebase service account +- Channel activation +- Web config and VAPID +- Advanced settings + +Recommended spacing: **24–32px**. + +Tailwind pattern: + +```html +
+ + +
+
...
+
...
+
...
+
+
+``` + +### Layer 2 — Subsections inside a section + +Use this for nested panels such as: + +- Saved key preview +- Upload area +- Channel item +- VAPID key group + +Recommended spacing: **16–24px**. + +```html +
+
+

Service account JSON

+

Server-side secret used by Firebase Admin SDK.

+
+ +
+
Upload JSON file
+
Saved key
+
+
+``` + +### Layer 3 — Fields inside a subsection + +Use this for labels, inputs, helpers, errors, and checkboxes. + +Recommended spacing: **4–16px** depending on the relationship. + +```html +
+ + +

Firebase Console → Project settings → General → Your apps.

+
+``` + +### Rule of thumb + +If two blocks have visible borders, they need a visible gap between them. + +For dense admin UI: + +```txt +Major sections: gap-section / gap-page = 24–32px +Nested subsections: gap-group / gap-section = 16–24px +Fields: gap-micro / gap-field = 4–12px +``` + +Do **not** do this: + +```html +
...
+
...
+``` + +Do this: + +```html +
+
...
+
...
+
+``` + +--- + +## 4. Tailwind v4 token setup + +Create or update `app.css`, `globals.css`, or your main Tailwind entry file. + +Important naming note: avoid naming a color `page` if you also have a font-size class like `text-page`. In Tailwind, `text-*` can refer to both color and font size. This guide uses `canvas` for the page background and `page-title` for the large text token to avoid conflicts. + +```css +@import "tailwindcss"; + +@theme { + /* Fonts */ + --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Inter, "Noto Sans", Arial, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + + /* Type scale */ + --text-caption: 0.75rem; + --text-caption--line-height: 1rem; + --text-caption--letter-spacing: 0.02em; + + --text-ui: 0.875rem; + --text-ui--line-height: 1.25rem; + --text-ui--letter-spacing: 0.01em; + + --text-body: 1rem; + --text-body--line-height: 1.5rem; + + --text-title: 1.25rem; + --text-title--line-height: 1.75rem; + + --text-page-title: 1.5rem; + --text-page-title--line-height: 2rem; + + /* Spacing scale */ + --spacing-micro: 0.25rem; /* 4px */ + --spacing-tight: 0.5rem; /* 8px */ + --spacing-field: 0.75rem; /* 12px */ + --spacing-group: 1rem; /* 16px */ + --spacing-section: 1.5rem; /* 24px */ + --spacing-page: 2rem; /* 32px */ + + /* Control heights */ + --spacing-control-sm: 2rem; /* 32px, compact desktop only */ + --spacing-control: 2.5rem; /* 40px, default dense input */ + --spacing-control-lg: 3rem; /* 48px, touch / mobile */ + + /* Radius */ + --radius-control: 0.5rem; + --radius-panel: 0.75rem; + + /* Semantic colors */ + --color-canvas: #0b0d12; + --color-surface: #11141b; + --color-surface-raised: #151922; + --color-surface-hover: #1a202b; + + --color-fg: #e8ecf1; + --color-fg-subtle: #b3bbc7; + --color-fg-muted: #8d96a3; + + --color-line: #28313d; + --color-line-strong: #5c6b7c; + + --color-focus: #7ab0ff; + --color-danger: #ff6b6b; + --color-success: #44d17a; + --color-warning: #ffd166; +} +``` + +--- + +## 5. Base and component classes + +```css +@layer base { + html { + font-family: var(--font-sans); + } + + body { + @apply bg-canvas text-fg text-ui antialiased; + } + + *:focus-visible { + @apply outline-2 outline-offset-2 outline-focus; + } +} + +@layer components { + /* Page layout */ + .page-shell { + @apply min-h-screen bg-canvas text-fg; + } + + .page-container { + @apply mx-auto w-full max-w-7xl px-group py-section md:px-page md:py-page; + } + + .page-header { + @apply mb-section space-y-tight; + } + + .page-title { + @apply text-title font-semibold tracking-normal text-fg md:text-page-title; + } + + .page-description { + @apply max-w-[72ch] text-ui text-fg-subtle; + } + + /* Critical anti-glue class */ + .section-stack { + @apply grid gap-section; + } + + @media (min-width: 768px) { + .section-stack { + @apply gap-page; + } + } + + .section { + @apply rounded-panel border border-line bg-surface p-group md:p-section; + } + + .section-header { + @apply mb-group space-y-micro; + } + + .section-title { + @apply text-body font-semibold tracking-normal text-fg; + } + + .section-description { + @apply max-w-[72ch] text-ui text-fg-subtle; + } + + /* Nested cards inside a large section */ + .subsection-stack { + @apply grid gap-group; + } + + .subsection { + @apply rounded-control border border-line bg-surface-raised p-group; + } + + .subsection-header { + @apply mb-field space-y-micro; + } + + .subsection-title { + @apply text-ui font-semibold text-fg; + } + + .subsection-description { + @apply text-caption text-fg-subtle; + } + + /* Forms */ + .form-grid { + @apply grid gap-field md:grid-cols-2 md:gap-group; + } + + .field { + @apply grid min-w-0 gap-micro; + } + + .field-span-full { + @apply md:col-span-2; + } + + .label { + @apply text-ui font-semibold text-fg; + } + + .help, + .meta { + @apply max-w-[60ch] text-caption text-fg-subtle; + } + + .validation { + @apply max-w-[60ch] text-caption font-semibold text-danger; + } + + .input, + .select, + .textarea { + @apply min-h-control w-full rounded-control border border-line-strong bg-surface px-field py-tight text-ui text-fg; + } + + .textarea { + @apply min-h-control-lg resize-y; + } + + .input::placeholder, + .textarea::placeholder { + @apply text-fg-muted; + } + + .input:focus-visible, + .select:focus-visible, + .textarea:focus-visible { + @apply outline-2 outline-offset-2 outline-focus; + } + + .checkbox-row { + @apply flex items-start gap-tight text-ui text-fg-subtle; + } + + .checkbox { + @apply mt-[0.1875rem] size-4 shrink-0 rounded border border-line-strong bg-surface; + } + + /* Buttons */ + .button-row { + @apply flex flex-wrap items-center gap-tight; + } + + .button { + @apply inline-flex min-h-control-sm items-center justify-center gap-tight rounded-control border border-line-strong px-field text-ui font-semibold text-fg; + } + + .button-primary { + @apply border-focus bg-focus text-canvas; + } + + .button-secondary { + @apply bg-surface hover:bg-surface-hover; + } + + /* Data and code */ + .num { + @apply text-right tabular-nums; + font-variant-numeric: tabular-nums lining-nums; + font-feature-settings: "tnum" 1, "lnum" 1; + } + + .code, + .id, + .hash { + @apply font-mono; + } + + /* Tables */ + .table-wrap { + @apply overflow-x-auto rounded-panel border border-line bg-surface; + } + + .table { + @apply w-full border-separate border-spacing-0; + } + + .table th { + @apply border-b border-line px-field py-tight text-left align-top text-caption font-semibold text-fg-subtle; + } + + .table td { + @apply border-b border-line px-field py-[0.625rem] align-top text-ui text-fg; + } + + .table tr:last-child td { + @apply border-b-0; + } + + .table th.num, + .table td.num { + @apply text-right; + } + + .table tbody tr:hover { + @apply bg-surface-hover; + } +} + +@media (prefers-reduced-motion: reduce) { + * { + transition-duration: 0ms !important; + animation-duration: 0ms !important; + scroll-behavior: auto !important; + } +} +``` + +--- + +## 6. Recommended layout for settings screens + +Use this structure for admin settings pages. + +```html +
+
+ + +
+
+
+

Service account JSON

+

+ Server-side secret for Firebase Admin SDK. It must never be exposed to the frontend. +

+
+ +
+
+
+ +
+

+ The file is read in the browser and sent to the server only when you click Save. +

+
+ +
+
+

Saved key

+

Only non-sensitive metadata is displayed.

+
+ +
+
+
project_id
+
gfcafe-ru
+
+
+
client_email
+
firebase-adminsdk-***@gfcafe-ru.iam.gserviceaccount.com
+
+
+
+
+
+ +
+
+

Channel activation

+

+ Each push channel is registered independently. +

+
+ +
+ + + +
+
+ +
+
+

Web config and VAPID

+

+ Public Firebase config for browser push notifications. +

+
+ +
+
+ + +

Firebase Console → Project settings → General → Your apps.

+
+ +
+ + +

Firebase project identifier.

+
+ +
+ + +
+ +
+ + +
+
+
+
+
+
+``` + +--- + +## 7. Anti-patterns and fixes + +### Anti-pattern: Cards touch each other + +```html +
...
+
...
+
...
+``` + +Fix: + +```html +
+
...
+
...
+
...
+
+``` + +### Anti-pattern: Border is the only separator + +```html +
...
+
...
+``` + +Fix: + +```html +
+
...
+
...
+
+``` + +### Anti-pattern: Everything is `text-xs` + +```html + + +

Firebase Console → Project settings.

+``` + +Fix: + +```html + + +

Firebase Console → Project settings.

+``` + +### Anti-pattern: Helper text is too close to the next field + +```html +
+ + +

Firebase Console → Project settings.

+ + +
+``` + +Fix: + +```html +
+
+ + +

Firebase Console → Project settings.

+
+ +
+ + +
+
+``` + +--- + +## 8. Tailwind v3 fallback + +If you are still on Tailwind v3, put the same values into `tailwind.config.js`. + +```js +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,ts,jsx,tsx,html}'], + theme: { + extend: { + fontFamily: { + sans: ['ui-sans-serif', 'system-ui', '-apple-system', 'Segoe UI', 'Inter', 'Noto Sans', 'Arial', 'sans-serif'], + mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Consolas', 'Liberation Mono', 'monospace'], + }, + fontSize: { + caption: ['0.75rem', { lineHeight: '1rem', letterSpacing: '0.02em' }], + ui: ['0.875rem', { lineHeight: '1.25rem', letterSpacing: '0.01em' }], + body: ['1rem', { lineHeight: '1.5rem' }], + title: ['1.25rem', { lineHeight: '1.75rem' }], + 'page-title': ['1.5rem', { lineHeight: '2rem' }], + }, + spacing: { + micro: '0.25rem', + tight: '0.5rem', + field: '0.75rem', + group: '1rem', + section: '1.5rem', + page: '2rem', + 'control-sm': '2rem', + control: '2.5rem', + 'control-lg': '3rem', + }, + borderRadius: { + control: '0.5rem', + panel: '0.75rem', + }, + colors: { + canvas: '#0b0d12', + surface: '#11141b', + 'surface-raised': '#151922', + 'surface-hover': '#1a202b', + fg: '#e8ecf1', + 'fg-subtle': '#b3bbc7', + 'fg-muted': '#8d96a3', + line: '#28313d', + 'line-strong': '#5c6b7c', + focus: '#7ab0ff', + danger: '#ff6b6b', + success: '#44d17a', + warning: '#ffd166', + }, + }, + }, + plugins: [], +}; +``` + +The component classes from the v4 example can stay almost the same in your CSS file if you use `@tailwind base; @tailwind components; @tailwind utilities;` and `@layer components`. + +--- + +## 9. Implementation checklist + +Use this checklist when reviewing dense UI screens. + +### Typography + +- [ ] Main UI text, labels, inputs, and table cells use `text-ui` or larger. +- [ ] Helper, caption, and meta text use `text-caption`, not smaller. +- [ ] Long descriptions use `text-body` or are moved into a dedicated help block. +- [ ] Labels are visible; placeholders are not used as field names. +- [ ] Page and section titles are only one or two steps larger than body text. +- [ ] No random `text-[13px]`, `leading-[17px]`, `tracking-[-0.02em]` in working UI. + +### Spacing + +- [ ] Major sections are wrapped in `.section-stack`. +- [ ] Major sections have `gap-section` or `gap-page` between them. +- [ ] Nested panels use `.subsection-stack`. +- [ ] Fields use `.field` and parent grids use `gap-field` / `gap-group`. +- [ ] Borders are never the only separator between large cards. +- [ ] Random spacing like `p-[7px]`, `gap-[11px]`, `mt-[13px]` is avoided. + +### Forms + +- [ ] Form layout starts with one column and moves to two columns only when helper text still fits. +- [ ] Each field follows `label → control → helper/error`. +- [ ] Input height is at least `min-h-control` for desktop forms. +- [ ] Checkbox rows have enough vertical padding when they behave like selectable cards. +- [ ] Errors are text messages, not only red borders. + +### Tables + +- [ ] Table body uses `text-ui`. +- [ ] Header uses `text-caption font-semibold`. +- [ ] Numeric columns use `.num`. +- [ ] Numeric columns are right-aligned. +- [ ] IDs, hashes, and code fragments use `.code`, `.id`, or `.hash`. +- [ ] Text is wrapped before it is truncated. + +### Accessibility + +- [ ] Text contrast is readable in both dark and light themes. +- [ ] Focus state is visible. +- [ ] The screen works at 125%, 150%, and 200% browser zoom. +- [ ] Dense controls are not smaller than 24×24px for interactive targets. +- [ ] Reduced motion is respected. + +--- + +## 10. Quick rules for developers + +1. Use `text-ui` as the default working size. +2. Use `text-caption` only for secondary text, never for primary controls. +3. Use `.section-stack` for page-level blocks. +4. Use `.subsection-stack` for nested panels. +5. Use `.field` for label/control/helper groups. +6. Use borders plus gaps, not borders instead of gaps. +7. Use `tabular-nums` for numeric data. +8. Do not use all caps for normal admin labels. +9. Avoid arbitrary spacing unless there is a real reason. +10. When the UI feels glued together, first increase block gaps before changing fonts. + +--- + +## 11. Minimal copy-paste package + +If you only need the essential part, start with these classes: + +```css +@layer components { + .section-stack { + @apply grid gap-section md:gap-page; + } + + .section { + @apply rounded-panel border border-line bg-surface p-group md:p-section; + } + + .section-header { + @apply mb-group space-y-micro; + } + + .section-title { + @apply text-body font-semibold text-fg; + } + + .section-description { + @apply max-w-[72ch] text-ui text-fg-subtle; + } + + .subsection-stack { + @apply grid gap-group; + } + + .subsection { + @apply rounded-control border border-line bg-surface-raised p-group; + } + + .field { + @apply grid gap-micro; + } + + .label { + @apply text-ui font-semibold text-fg; + } + + .help { + @apply max-w-[60ch] text-caption text-fg-subtle; + } + + .input { + @apply min-h-control w-full rounded-control border border-line-strong bg-surface px-field py-tight text-ui text-fg; + } +} +``` + +Use it like this: + +```html +
+
+
+

Service account JSON

+

Server-side Firebase Admin SDK secret.

+
+ +
+
+
+ + +

Use the Firebase project identifier.

+
+
+
+
+ +
+
+

Channel activation

+

Enable mobile or web push independently.

+
+
+
+``` + +This is the main anti-glue pattern for dense Tailwind interfaces. diff --git a/docs/UIComponents.md b/docs/UIComponents.md index 37ff5107..a46924b7 100644 --- a/docs/UIComponents.md +++ b/docs/UIComponents.md @@ -727,5 +727,6 @@ const { VanillaJSONEditor } = window.JSComponents; | `window.ReactDOM` | ReactDOM 19 | | `window.LucideReact` | All Lucide icons (`window.LucideReact.Pencil`, etc.) | | `window.InertiajsReact` | Inertia.js React adapter | -| `window.axios` | Axios HTTP client | +| `window.adminApi` | Typed HTTP client for API calls. Use `*Json` methods for JSON endpoints. | +| `window.axios` | Legacy Axios-compatible compatibility client. Logs a deprecation message; prefer `window.adminApi`. | | `window.sonner` | Sonner toast API (`toast`, `toast.error`, `toast.promise`, etc.) | diff --git a/docs/configuration.md b/docs/configuration.md index 6cf1467e..8767d7f3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,4 +1,6 @@ -# Admin Panel configuration +# Admin Panel configuration + +> This page is a legacy extended reference. Prefer the focused pages in `docs/Configuration/` for new projects. Sequelize is the primary supported ORM; TypeORM support is experimental. ### Abstract @@ -17,10 +19,10 @@ Typescript example: import { AdminizerConfig } from "adminizer"; -const config: AdminizerConfig = { - routePrefix: "/admin", - auth: true, - dashboard: true, +const config: AdminizerConfig = { + routePrefix: "/admin", + auth: { enable: true }, + dashboard: true, models: { ExampleModel: { title: "Example Models", @@ -70,12 +72,28 @@ Admin panel configuration consist of this options: |-------------------|-------------------------- | `routePrefix` | Route prefix for admin panel. Default: `/admin` | `linkAssets` | Will create a symlink to Admin panel assets. Anyway AP will try to load all assets from /admin/**** and you could copy them manually -| `identifierField` | Default identifier field into models. This field will be used as identifier. Default: `id` -| `models` | Configuration for models. Read below... -| `showORMtime` | Set `true` for enable showing fields createdAt and updatedAt in edit and add sections -## Models - -Admin panel divided into `models` and this is a main part of configuration. +| `identifierField` | Default identifier field into models. This field will be used as identifier. Default: `id` +| `models` | Configuration for models. Read below... +| `showORMtime` | Set `true` for enable showing fields createdAt and updatedAt in edit and add sections + +### List defaults + +Use the global `list.defaultPageSize` option to set how many records are shown on model list pages by default. Supported values are `5`, `20`, and `50`; the default value is `50`. + +```typescript +const config: AdminizerConfig = { + list: { + defaultPageSize: 20, + }, + models: { + // ... + }, +}; +``` + +## Models + +Admin panel divided into `models` and this is a main part of configuration. Model will represent several actions for each model that you need to enable into Admin Panel. It will consist of actions: @@ -349,10 +367,9 @@ module.exports.adminpanel = { ## Associations -Now Adminpanel hook partially supports assotiations. - -Adminpanel determinates such fields by waterline configuration. -Right now if you model field have such configuration: +Adminizer partially supports associations. + +Adminizer detects association fields from adapter metadata. For example, a relation field may be represented as: ```javascript fieldName: { @@ -374,35 +391,26 @@ owner: { } ``` -## Auto config - -This configuration loads all sail models as they are. Just place in `config\adminpanel.js` - -```javascript - const fs = require("fs"); - let modelPath = __dirname + "/../api/models"; - let models = {}; - fs.readdir(modelPath, function (err, files) { - files.forEach(function (file) { - let modelName = file.split(".")[0]; - models[modelName] = { - title: modelName, - model: modelName, - }; - }); - }); - - module.exports.adminpanel = { models: models } ; -``` +## Auto config + +The old Sails auto-config example is not applicable to current Adminizer projects. Define models explicitly in `AdminpanelConfig.models`, or generate that object from your ORM metadata in project code. + +```ts +const config = { + models: { + Example: { + title: "Example", + model: "example", + }, + }, +}; +``` ## Limitations -+ For now admin panel do not fully support waterline associations. So some fields might be ignored ! It's planned. -+ No file upload functionality. Due to the nature of `skipper` I removed file upload functionality. **Not tested with latest one** -+ No custom actions support. For now you couldn't add custom actions and pages into admin panel. -+ No template engine support except of `jade`. I primary working with this template engine and didn't create a another templates -+ No custom assets support. You couldn't edit css/js for admin panel for now. ++ Association support depends on the active adapter and model metadata. ++ Custom controls, modules, widgets, media manager uploads, and custom assets are supported by current Adminizer versions. See the focused documentation pages for current APIs. ## Configuration schema @@ -414,7 +422,7 @@ This configuration loads all sail models as they are. Just place in `config\adm /** @type {import("adminizer").AdminizerConfig} */ const config = { routePrefix: "/admin", - auth: true, + auth: { enable: true }, dashboard: true, models: { ExampleModel: { @@ -606,25 +614,19 @@ module.exports = config; title: string text: string } - translation: { // Text translation using sails built-in internationalization - locales: string[] // Locales list - path: string // Relative path from project root to translations folder - defaultLocale: string // Default locale - } + translation: { // Text translation + locales: string[] // Locales list + directory: string // Relative path from project root to translations folder + defaultLocale: string // Default locale + } // default administrator login credentials, will be used if no admin profiles found administrator: { login: string password: string } - // forms - forms: { - path: string - data: object - get: ()=>void - set: ()=>void - } - // Enable/disable displaying createdAt and updatedAt fields in `edit` and `add` sections - showORMtime: boolean + // Legacy configuration-driven forms are no longer used starting with Adminizer 5. + // Enable/disable displaying createdAt and updatedAt fields in `edit` and `add` sections + showORMtime: boolean package: any // Adminpanel package.json config timezones: { // Available timezones list id: string diff --git a/docs/development.md b/docs/development.md index cd117f4e..0a517586 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,45 +1,70 @@ ## Development - **`npm run dev`** Starts the application in development mode using Sequelize with file watching. -- **`npm run dev:waterline`** - Starts the application in development mode using Waterline with file watching. - -- **`npm run watch:backend`** - Watches for changes in backend files and recompiles TypeScript continuously. - -## Build Commands -- **`npm run build:assets`** - Builds frontend assets using Vite. - -- **`npm run build:backend`** - Combines backend copy and compilation steps. - -- **`npm run build`** - Full build process: copies backend, compiles backend, and builds assets. - -## Module-specific Builds (this is just for tests, as an example, you should create your own commands to build modules) -- **`npm run build:module`** - Builds test modules using a custom Vite config. - -- **`npm run build:react-quill`** - Builds the React-Quill WYSIWYG module using a custom Vite config. - -- **`npm run copy:modules`** - Copies modules using `copy-modules.js` script. - -- **`npm run build:assets:modules`** - Builds assets and copies modules. - -## Demo & Seeding -- **`npm run start:seed`** - Starts the application with seed data enabled (using `SEED_DATA=true`). - -- **`npm run demo:build`** - Prepares a demo build: copies backend, compiles backend, builds assets, and copies modules. - -- **`npm run demo`** - Alias for `start:seed` - runs the demo with seeded data. - -## Preview mode -- **`npm start`** - Starts the application using TSX with the Waterline fixture configuration. +- **`npm run dev:typeorm`** + Starts the application in development mode using the experimental TypeORM fixture with file watching. +- **`npm run dev:no-seed`** + Starts the Sequelize fixture in development mode without seed data. +- **`npm run dev:typeorm:no-seed`** + Starts the TypeORM fixture in development mode without seed data. +- **`npm run dev:no-seed-clean`** + Starts the Sequelize fixture without seed data and cleans `.tmp` before startup. +- **`npm run dev:cors`** + Starts the Sequelize fixture with CORS settings for frontend integration testing. + +- **`npm run tsc:watch`** + Watches for changes in backend files and recompiles TypeScript continuously. + +Adminizer uses `ADMINIZER_ENV=dev` to switch on development behavior such as +Vite middleware and dev module paths. + +## Build Commands +- **`npm run build:assets`** + Builds frontend assets using Vite. + +- **`npm run build:backend`** + Combines backend copy and compilation steps. + +- **`npm run compile:backend`** + Compiles backend TypeScript and appends `.js` extensions in generated imports. + +- **`npm run compile:ui`** + Compiles the shared UI TypeScript package. + +- **`npm run build`** + Full build process: copies backend, compiles backend, compiles UI, and builds assets. + +## Module-specific Builds (this is just for tests, as an example, you should create your own commands to build modules) +- **`npm run build:module`** + Builds test modules using a custom Vite config. + +- **`npm run build:react-quill`** + Builds the React-Quill WYSIWYG module using a custom Vite config. + +- **`npm run copy:modules`** + Copies modules using `copy-modules.js` script. + +- **`npm run build:assets:modules`** + Builds assets and copies modules. + +## Demo & Seeding +- **`npm run start`** + Starts the Sequelize fixture without seed data. + +- **`npm run start:typeorm`** + Starts the experimental TypeORM fixture without seed data. + +- **`npm run start:seed`** + Starts the Sequelize fixture with seed data enabled. + +- **`npm run start:typeorm:seed`** + Starts the experimental TypeORM fixture with seed data enabled. + +- **`npm run demo:build`** + Prepares a demo build: copies backend, compiles backend, builds assets, and copies modules. + +- **`npm run demo`** + Runs the Sequelize demo with seeded data. + +- **`npm run demo:typeorm`** + Runs the experimental TypeORM demo with seeded data. diff --git a/docs/index.md b/docs/index.md index 7816e569..1beb0f80 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,14 +9,20 @@ * [General Settings](Configuration/General.md) * [Model Definitions](Configuration/Models.md) * [Field Options](Configuration/Fields.md) +* [Inline Editing](Configuration/InlineEditing.md) * [Custom Components](Configuration/CustomComponents.md) ## Other +* [Commands](Commands.md) +* [Development](development.md) * [Localization](Configuration/Localization.md) * [Icons](Icons.md) * [Notifications](Notifications.md) * [History-Actions](HistoryActions.md) +* [Internal Queries](InternalQueries.md) +* [AI Assistant](AiAssistant.md) +* [Feedback](Feedback.md) ## 3. Frontend Integration * [Inertia Adapter & Flash](InertiaAdapter.md) @@ -27,11 +33,16 @@ * [Admin Controls](Controls.md) * [Navigation Catalog](Navigation.md) +* [Filters](Filters.md) +* [Export](Export.md) +* [Feed Export](FeedExport.md) +* [Widgets](Configuration/Widgets.md) ## 5. Access Control -* [Model-Level Permissions](AccessControl/ModelPermissions.md) -* [Field-Level Restrictions](AccessControl/FieldRestrictions.md) -* [Role & Group Management](AccessControl/RoleGroups.md) +* [Authorization](Authorization.md) +* [Access Rights Tokens](AccessRights/access-rights-tokens.md) +* [Field-Level Restrictions](AccessRights/AccessRightsModelFields.md) +* [User-Owned Records](AccessRights/user-owned-records.md) * [Global UI Components](GlobalUI.md) * [UI Component List](UIComponents.md) diff --git a/fixture/adminizerConfig.ts b/fixture/adminizerConfig.ts index b6ca318c..0753ec9f 100644 --- a/fixture/adminizerConfig.ts +++ b/fixture/adminizerConfig.ts @@ -7,7 +7,7 @@ const models: AdminpanelConfig["models"] = { title: 'Test model', model: 'test', displayName: 'title', - userAccessRelation: 'owner', + // userAccessRelation: 'owner', fields: { createdAt: false, updatedAt: false, @@ -46,37 +46,33 @@ const models: AdminpanelConfig["models"] = { title: 'All controls', model: 'example', displayName: 'description', - userAccessRelation: 'owner', - tools: [ - { - id: '1', - link: `https://google.com`, - type: 'blank', - title: 'Some new action', - icon: 'reorder', + filters: { + enabled: true, + excludeFromFilters: ['createdAt', 'id', 'ownerId', 'testRelation', 'testRelationExample'], + }, + // userAccessRelation: 'owner', + // tools: [ + // { + // id: '1', + // link: `https://google.com`, + // type: 'blank', + // title: 'Some new action', + // icon: 'reorder', + // }, + // ], + fields: { + createdAt: { + title: 'created At', + type: 'string', }, - { - id: '2', - link: `${routePrefix}/form/global`, - type: 'self', - title: 'Form example', - icon: 'payment', + updatedAt: { + visible: false }, - { - id: '3', - link: 'https://google.com', - type: 'blank', - title: 'Form example from file Form example from file', - icon: 'touch_app', - } - ], - fields: { - createdAt: false, - updatedAt: false, title: { title: 'Title', type: 'string', - required: true + required: true, + // inlineEditable: false }, description: { title: 'Textarea', @@ -86,7 +82,8 @@ const models: AdminpanelConfig["models"] = { }, sort: { type: 'boolean', - title: 'Boolean' + title: 'Boolean', + // inlineEditable: true }, disabled_text: { title: 'Disabled', @@ -100,7 +97,8 @@ const models: AdminpanelConfig["models"] = { options: { min: 10, max: 80 - } + }, + // inlineEditable: true }, select: { title: 'Select', @@ -135,6 +133,7 @@ const models: AdminpanelConfig["models"] = { number: { title: 'Number', type: 'number', + // inlineEditable: true }, color: { title: 'color', @@ -145,7 +144,11 @@ const models: AdminpanelConfig["models"] = { type: 'week', }, json: { - type: 'jsoneditor' + type: 'jsoneditor', + customFilter: { + handlerId: 'Example.json', + label: 'Custom filtering' + } }, tui: { type: 'tuieditor', @@ -173,6 +176,9 @@ const models: AdminpanelConfig["models"] = { datatable: { title: 'Price', type: 'table', + customFilter: { + handlerId: 'Example.datatable' + }, options: { config: { dataSchema: { name: null, footage: null, price: null }, @@ -190,10 +196,6 @@ const models: AdminpanelConfig["models"] = { isIn: ['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], type: 'select-many' }, - checkboxes: { - title: 'Checkboxes', - isIn: ['one', 'two', 'three'] - }, editor: { title: 'Editor', type: 'wysiwyg', @@ -227,7 +229,7 @@ const models: AdminpanelConfig["models"] = { } } }, - testRelation: { + testRelationExample: { title: 'Test one association', displayModifier: function (data) { return data?.title; @@ -236,30 +238,42 @@ const models: AdminpanelConfig["models"] = { }, tests: { title: 'One to many association', - displayModifier: function (data) { - return data?.title; + displayModifier: function (data: any) { + if (Array.isArray(data)) { + return data + .map((item: any) => item?.title) + .filter(Boolean) + .join(', '); + } + return data?.title || ''; + }, + disabled: false + }, + owner: { + title: 'Owner', + displayModifier: function (data: any) { + return data?.login || ''; }, disabled: false }, }, list: { fields: { - json: false, - tui: false, - geojson: false, - week: false, - color: false, - range: false, - date: false, - month: false, - selectMany: false, - select: false, - dateTime: false, - testRelation: false, - tests: false, - price: false, - code: false, - datatable: false + // json: false, + // tui: false, + // geojson: false, + // week: false, + // color: false, + // range: false, + // date: false, + // month: false, + // selectMany: false, + // select: false, + // testRelation: false, + // tests: false, + // price: false, + // code: false, + // datatable: false }, actions: { global: [ @@ -287,12 +301,6 @@ const models: AdminpanelConfig["models"] = { type: 'blank', title: 'Google', icon: 'insert_link' - }, { - id: "5", - link: `${routePrefix}/form/global`, - type: 'self', - title: 'Form', - icon: 'insert_link' }, ], inline: [ @@ -400,7 +408,9 @@ const models: AdminpanelConfig["models"] = { createdAt: { title: 'Created at', }, - updatedAt: false, + updatedAt: { + visible: false + }, mediamanager_one: { title: 'Images 1', type: 'mediamanager', @@ -451,13 +461,28 @@ const models: AdminpanelConfig["models"] = { visible: false, }, fields: { - createdAt: false, - updatedAt: false, + createdAt: { + visible: false + }, + updatedAt: { + visible: false + } } }, } const config: AdminpanelConfig = { + list: { + defaultPageSize: 5 + }, + filters: { + enabled: true + }, + system: { + internalModelAccess: { + "test-catalog": ["TestCatalog"] + } + }, mediamanager: { fileStoragePath: '.tmp/public', allowMIME: ['image/*', 'application/*', 'text/*', 'video/*'], @@ -551,152 +576,14 @@ const config: AdminpanelConfig = { } ], }, - forms: { - data: { - global: { - field1: { - title: 'Field1', - type: 'string', - value: 'Some string', - required: true, - }, - mediamanager: { - title: 'Images', - type: 'mediamanager', - options: { - id: "default", - group: 'form_global_images', - initTab: "table-application", - accept: ["application/pdf", "application/msword"] - }, - value: null - }, - field2: { - title: 'Field2', - type: 'text', - value: 'Some text', - required: true, - tooltip: 'tooltip for field2', - }, - json: { - title: 'Json', - type: 'jsoneditor' - }, - text: { - title: 'Editor', - type: 'wysiwyg', - }, - tabs_video: { - "title": "Табы видео", - "type": "table", - "tooltip": "Нажмите на таблице правой клавишей мыши, выберите \"Вставить строку ниже\", в добавленную строку вставьте код полученный на сервисе ВКонтакте или YouTube через кнопку \"Поделиться\"", - "options": { - "config": { - "dataSchema": { - "iframe": null - }, - "colHeaders": [ - "Iframe" - ], - "rowHeaders": true, - "columns": [ - { - "data": "iframe" - } - ], - "height": "auto", - "width": "100%", - "manualColumnResize": true, - "contextMenu": true, - "licenseKey": "non-commercial-and-evaluation" - } - }, - "value": null - }, - tabs_tests: { - "title": "Табы онлайн-тесты", - "tooltip": "Нажмите на таблице правой клавишей мыши, выберите \"Вставить строку ниже\", в соответствующих столбцах вставьте ссылку на тест и его название", - "type": "table", - "options": { - "config": { - "dataSchema": { - "link": null, - "text": null - }, - "colHeaders": [ - "Ссылка", - "Текст" - ], - "rowHeaders": true, - "columns": [ - { - "data": "link" - }, - { - "data": "text" - } - ], - "height": "auto", - "width": "100%", - "manualColumnResize": true, - "contextMenu": true, - "licenseKey": "non-commercial-and-evaluation" - } - }, - "value": null - }, - tabs_links: { - "title": "Табы полезные ссылки", - "type": "table", - "tooltip": "Нажмите на таблице правой клавишей мыши, выберите \"Вставить строку ниже\", в соответствующих столбцах вставьте ссылку на сайт и его название", - "options": { - "config": { - "dataSchema": { - "link": null, - "text": null - }, - "colHeaders": [ - "Ссылка", - "Текст" - ], - "rowHeaders": true, - "columns": [ - { - "data": "link" - }, - { - "data": "text" - } - ], - "height": "auto", - "width": "100%", - "manualColumnResize": true, - "contextMenu": true, - "licenseKey": "non-commercial-and-evaluation" - } - }, - "value": null - } - } - } - }, navbar: { additionalLinks: [ - { - id: '1', - link: `${routePrefix}/form/global`, - title: 'Global Settings', - type: 'self', - icon: 'build', - accessRightsToken: 'read-global-form' - }, { id: '2', link: `${routePrefix}/module-test`, type: 'self', title: 'Test Module', - icon: '360', - accessRightsToken: 'read-global-form' + icon: '360' }, { id: '3', @@ -743,13 +630,6 @@ const config: AdminpanelConfig = { type: 'blank', icon: 'share' }, - { - id: "2", - title: 'Sub 3 Sub 3 Sub 3 Sub 3', - link: `${routePrefix}/form/global`, - type: 'self', - icon: 'insert_link' - }, { id: "3", title: 'Sub 4', @@ -806,19 +686,13 @@ const config: AdminpanelConfig = { }, translation: { locales: ['en', 'ru', 'de', 'ua'], - //path: 'fixture/locales', // relative path to translations directory + directory: 'fixture/locales', // relative path to translations directory + // missingTranslationDirectory: 'fixture/locales_missing', defaultLocale: 'en' }, models: models, //@ts-ignore generator: {}, - globalSettings: { // Global project settings - enableMigrations: true - }, - migrations: { - path: 'mg_path', // path to migrations - //config: string | object // db-migrate config - }, showVersion: { link: 'https://docs.adminizer.org', hint: 'Adminizer documentation', diff --git a/fixture/cors-api/api.ts b/fixture/cors-api/api.ts index a7a789ea..87956912 100644 --- a/fixture/cors-api/api.ts +++ b/fixture/cors-api/api.ts @@ -54,7 +54,10 @@ export function corsApi(adminizer: Adminizer) { adminizer.app.post(`${routePrefix}/api/auth/login`, async (req: any, res: any) => { try { const {login, password} = req.body; - const user = await req.adminizer.modelHandler.model.get("UserAP")["_findOne"]({login: login}) as UserAP; + const user = await adminizer.modelHandler + .internal("auth") + .get("UserAP") + .findOne({where: {login: login}}); const token = signUser(user, frontendJwtSecret); @@ -83,4 +86,4 @@ export function corsApi(adminizer: Adminizer) { }); } }); -} \ No newline at end of file +} diff --git a/fixture/filters/customFilterHandlers.ts b/fixture/filters/customFilterHandlers.ts new file mode 100644 index 00000000..1f16fe9d --- /dev/null +++ b/fixture/filters/customFilterHandlers.ts @@ -0,0 +1,255 @@ +import { Sequelize } from 'sequelize'; +import { Brackets } from 'typeorm'; +import { AbstractCustomFilter, FilterCustomFieldCondition } from '../../dist'; + +const TYPEORM_ROOT_ALIAS = 'record'; + +function normalizeSearchValue(value: unknown): string { + if (typeof value === 'string') { + return value.trim(); + } + + if (value === null || value === undefined) { + return ''; + } + + return JSON.stringify(value); +} + +function extractJsonPairFilters(rawValue: string): { key?: string; value?: string } { + const keyMatch = rawValue.match(/"key"\s*:\s*"([^"]+)"/i); + const valueMatch = rawValue.match(/"value"\s*:\s*"([^"]+)"/i); + + return { + key: keyMatch?.[1], + value: valueMatch?.[1], + }; +} + +function escapeSql(value: string): string { + return value.replace(/'/g, "''"); +} + +function likePattern(value: string): string { + return `%${value}%`; +} + +export class ExampleJsonCustomFilterHandler extends AbstractCustomFilter { + public readonly name = 'Example JSON text search'; + public readonly description = 'Search by substring in JSON text'; + public readonly inputConfig = { + query: { placeholder: 'JSON text', type: 'text' as const } + }; + + constructor() { + super('Example', 'json'); + } + + public buildCondition(value: unknown): FilterCustomFieldCondition { + const qualifiedJsonColumn = '`Example`.`json`'; + const valueFromInput = typeof value === 'object' && value !== null && !Array.isArray(value) + ? (value as Record).query + : value; + const originalValue = normalizeSearchValue(valueFromInput); + + if (!originalValue) { + return { criteria: {} }; + } + + const compactValue = originalValue.replace(/\s+/g, '').replace(/,+$/g, ''); + const pairFilters = extractJsonPairFilters(originalValue); + const sqlClauses: string[] = []; + + if (pairFilters.key) { + sqlClauses.push(`json_extract(${qualifiedJsonColumn}, '$.key') LIKE '%${escapeSql(pairFilters.key)}%'`); + } + + if (pairFilters.value) { + sqlClauses.push(`json_extract(${qualifiedJsonColumn}, '$.value') LIKE '%${escapeSql(pairFilters.value)}%'`); + } + + if (sqlClauses.length > 0) { + return { + criteria: Sequelize.literal(sqlClauses.join(' AND ')) + }; + } + + return { + criteria: Sequelize.literal( + `(CAST(${qualifiedJsonColumn} AS TEXT) LIKE '%${escapeSql(originalValue)}%' OR CAST(${qualifiedJsonColumn} AS TEXT) LIKE '%${escapeSql(compactValue)}%')` + ) + }; + } +} + +function parseOptionalNumber(rawValue: unknown): number | null { + if (rawValue === undefined || rawValue === null) { + return null; + } + + if (typeof rawValue === 'number') { + return Number.isFinite(rawValue) ? rawValue : null; + } + + const normalized = String(rawValue).trim().replace(',', '.'); + if (!normalized) { + return null; + } + + if (!/^-?\d+(\.\d+)?$/.test(normalized)) { + return null; + } + + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : null; +} + +export class ExampleDatatablePriceRangeFilterHandler extends AbstractCustomFilter { + public readonly name = 'Price range'; + public readonly description = 'Filter by price range inside datatable JSON array'; + public readonly inputConfig = { + from: { placeholder: 'Price from', type: 'number' as const }, + to: { placeholder: 'Price to', type: 'number' as const }, + }; + + constructor() { + super('Example', 'datatable'); + } + + public buildCondition(value: unknown): FilterCustomFieldCondition { + const qualifiedColumn = '`Example`.`datatable`'; + const valueObject = typeof value === 'object' && value !== null && !Array.isArray(value) + ? value as Record + : {}; + + let from = parseOptionalNumber(valueObject.from); + let to = parseOptionalNumber(valueObject.to); + + if (from !== null && to !== null && from > to) { + [from, to] = [to, from]; + } + + if (from === null && to === null) { + return { criteria: {} }; + } + + const clauses: string[] = []; + if (from !== null) { + clauses.push(`CAST(json_extract(price_item.value, '$.price') AS REAL) >= ${from}`); + } + if (to !== null) { + clauses.push(`CAST(json_extract(price_item.value, '$.price') AS REAL) <= ${to}`); + } + + const sql = `EXISTS (SELECT 1 FROM json_each(COALESCE(${qualifiedColumn}, '[]')) AS price_item WHERE ${clauses.join(' AND ')})`; + + return { + criteria: Sequelize.literal(sql) + }; + } +} + +export class TypeOrmExampleJsonCustomFilterHandler extends AbstractCustomFilter { + public readonly name = 'Example JSON text search'; + public readonly description = 'Search by substring in JSON text'; + public readonly inputConfig = { + query: { placeholder: 'JSON text', type: 'text' as const } + }; + + constructor() { + super('Example', 'json'); + } + + public buildCondition(value: unknown): FilterCustomFieldCondition { + const valueFromInput = typeof value === 'object' && value !== null && !Array.isArray(value) + ? (value as Record).query + : value; + const originalValue = normalizeSearchValue(valueFromInput); + + if (!originalValue) { + return { criteria: {} }; + } + + const compactValue = originalValue.replace(/\s+/g, '').replace(/,+$/g, ''); + const pairFilters = extractJsonPairFilters(originalValue); + + return { + criteria: new Brackets((builder) => { + if (pairFilters.key || pairFilters.value) { + if (pairFilters.key) { + builder.andWhere(`json_extract(${TYPEORM_ROOT_ALIAS}.json, '$.key') LIKE :jsonKey`, { + jsonKey: likePattern(pairFilters.key), + }); + } + if (pairFilters.value) { + builder.andWhere(`json_extract(${TYPEORM_ROOT_ALIAS}.json, '$.value') LIKE :jsonValue`, { + jsonValue: likePattern(pairFilters.value), + }); + } + return; + } + + builder.where(`CAST(${TYPEORM_ROOT_ALIAS}.json AS TEXT) LIKE :jsonOriginal`, { + jsonOriginal: likePattern(originalValue), + }); + + if (compactValue !== originalValue) { + builder.orWhere(`CAST(${TYPEORM_ROOT_ALIAS}.json AS TEXT) LIKE :jsonCompact`, { + jsonCompact: likePattern(compactValue), + }); + } + }) as any + }; + } +} + +export class TypeOrmExampleDatatablePriceRangeFilterHandler extends AbstractCustomFilter { + public readonly name = 'Price range'; + public readonly description = 'Filter by price range inside datatable JSON array'; + public readonly inputConfig = { + from: { placeholder: 'Price from', type: 'number' as const }, + to: { placeholder: 'Price to', type: 'number' as const }, + }; + + constructor() { + super('Example', 'datatable'); + } + + public buildCondition(value: unknown): FilterCustomFieldCondition { + const valueObject = typeof value === 'object' && value !== null && !Array.isArray(value) + ? value as Record + : {}; + + let from = parseOptionalNumber(valueObject.from); + let to = parseOptionalNumber(valueObject.to); + + if (from !== null && to !== null && from > to) { + [from, to] = [to, from]; + } + + if (from === null && to === null) { + return { criteria: {} }; + } + + return { + criteria: new Brackets((builder) => { + const clauses: string[] = []; + const params: Record = {}; + + if (from !== null) { + clauses.push(`CAST(json_extract(price_item.value, '$.price') AS REAL) >= :datatablePriceFrom`); + params.datatablePriceFrom = from; + } + if (to !== null) { + clauses.push(`CAST(json_extract(price_item.value, '$.price') AS REAL) <= :datatablePriceTo`); + params.datatablePriceTo = to; + } + + builder.where( + `EXISTS (SELECT 1 FROM json_each(COALESCE(${TYPEORM_ROOT_ALIAS}.datatable, '[]')) AS price_item WHERE ${clauses.join(' AND ')})`, + params + ); + }) as any + }; + } +} diff --git a/fixture/helpers/seedDatabase.ts b/fixture/helpers/seedDatabase.ts index e8cb582f..c618961c 100644 --- a/fixture/helpers/seedDatabase.ts +++ b/fixture/helpers/seedDatabase.ts @@ -1,229 +1,356 @@ -import { faker, tr } from '@faker-js/faker'; -import { generate } from 'password-hash'; -import { UserAP } from '../../src'; -import { Example } from '../models/sequelize/Example'; -export async function seedDatabase( - collections: Record, - count: number = 3 -) { - const getRandomTime = () => { - const hours = faker.number.int({ min: 0, max: 23 }).toString().padStart(2, '0'); - const minutes = faker.number.int({ min: 0, max: 59 }).toString().padStart(2, '0'); - return `${hours}:${minutes}`; - }; - - const exampleModel = collections.example ?? collections.Example; - const testModel = collections.test ?? collections.Test; - const userModel = collections.userap ?? collections.UserAP; - const groupModel = collections.groupap ?? collections.GroupAP; - - - const isSequelize = typeof exampleModel?.bulkCreate === 'function'; - const isWaterline = typeof exampleModel?.createEach === 'function'; - - if (!exampleModel || !userModel || !groupModel || (!isSequelize && !isWaterline)) { - throw new Error('Models should support the ORM interface (Sequelize or Waterline)'); - } - - // ------------------ Groups ------------------ // - const groupNames = [ - { name: 'Admins', description: 'System administrators', tokens: [ - 'ai-assistant-dummy', - 'ai-assistant-openai', - ] }, - { name: 'Users', description: 'Registered users', tokens: - [ - "create-test-model", - "read-test-model", - "update-test-model", - "delete-test-model", - - "create-example-model", - "read-example-model", - "update-example-model", - - "read-jsonschema-model", - "ai-assistant-dummy", - "ai-assistant-openai", - ] - }, - { name: 'Guests', description: 'Guest access' }, - ]; - - for (const group of groupNames) { - const exists = isSequelize - ? await groupModel.findOne({ where: { name: group.name } }) - : await groupModel.find({ name: group.name }); - - if (!exists || (Array.isArray(exists) && exists.length === 0)) { - if (isSequelize) { - await groupModel.create(group); - } else { - await groupModel.create(group).fetch(); - } - } - } - - // ------------------ Users ------------------ // - const users = [ - { login: 'pass', password: 'pass', fullName: 'User Pass' }, - { login: 'demo', password: 'demo', fullName: 'Administrator', isAdministrator: true }, - { login: 'user1', password: 'user1', fullName: 'User One' }, - { login: 'admin', password: 'admin', fullName: 'Admin User', isAdministrator: true }, - { login: 'user2', password: 'user2', fullName: 'User Two' }, - { login: 'user3', password: 'user3', fullName: 'User Three', isConfirmed: true }, - { login: 'openai', password: 'openai', fullName: 'OpenAI Agent', isAdministrator: true, isConfirmed: true }, - ]; - - for (const u of users) { - const exists = isSequelize - ? await userModel.findOne({ where: { login: u.login } }) - : await userModel.find({ login: u.login }); - - if (!exists || (Array.isArray(exists) && exists.length === 0)) { - const userData = { - login: u.login, - password: u.password, - passwordHashed: generate(u.login+u.password+process.env.AP_PASSWORD_SALT), - fullName: u.fullName, - isActive: true, - isAdministrator: u.isAdministrator || false, - isConfirmed: u.isConfirmed - }; - - const userInstance = isSequelize - ? await userModel.create(userData) - : await userModel.create(userData).fetch(); - - - // Linking to groups (simplified logic) - const groupName = u.isAdministrator ? 'Admins' : 'Users'; - - const group = isSequelize - ? await groupModel.findOne({ where: { name: groupName } }) - : (await groupModel.find({ name: groupName }))[0]; - - if (group && group.addUser) { - await group.addUser(userInstance); // Sequelize M:N - } else if (isWaterline && group && typeof groupModel.addToCollection === 'function') { - await groupModel.addToCollection(group.id, 'users', userInstance.id); - } - } - } - - - // ------------------ Example Records ------------------ // - const exampleCount = isSequelize - ? await exampleModel.count() - : await exampleModel.count({}); - - const allUsers: UserAP[] = isSequelize - ? await userModel.findAll() - : await userModel.find(); - - if (exampleCount === 0) { - const fakeExamples = Array.from({ length: count }, () => { - const randomUser = faker.helpers.arrayElement(allUsers); - if(isSequelize) { - return { - title: faker.lorem.word(), - description: faker.lorem.paragraph(), - sort: faker.datatype.boolean(), - time: getRandomTime(), - ownerId: randomUser.id, - number: faker.number.int(300), - editor: faker.lorem.text(), - } - } else { - return { - title: faker.lorem.word(), - description: faker.lorem.paragraph(), - sort: faker.datatype.boolean(), - time: getRandomTime(), - owner: randomUser.id, - ownerId: randomUser.id, - number: faker.number.int(300), - editor: faker.lorem.text(), - } - } - } - ); - - if (isSequelize) { - await exampleModel.bulkCreate(fakeExamples); - } else { - await exampleModel.createEach(fakeExamples).fetch(); - } - } - - // ------------------ Tests ------------------ // - const testCount = isSequelize - ? await testModel.count() - : await testModel.count({}); - - if (testCount === 0) { - const allUsers: UserAP[] = isSequelize - ? await userModel.findAll() - : await userModel.find(); - - const allExamples: Example[] = isSequelize - ? await exampleModel.findAll() - : await exampleModel.find(); - - const fakeTests = Array.from({ length: count }, () => { - const randomUser = faker.helpers.arrayElement(allUsers); - const randomExample = faker.helpers.arrayElement(allExamples); - - if(isSequelize) { - return { - title: faker.lorem.words(3), - ownerId: randomUser.id, - exampleId: randomExample?.id ?? null, - }; - } else { - return { - title: faker.lorem.words(3), - owner: randomUser.id, - example: randomExample?.id ?? null, - } - } - }); - - if (isSequelize) { - await testModel.bulkCreate(fakeTests); - } else { - await testModel.createEach(fakeTests).fetch(); - } - } - - - // ------------------ JsonSchemas ------------------ // - const jsonSchemaModel = collections.jsonschema ?? collections.JsonSchema; - - const schemaCount = isSequelize - ? await jsonSchemaModel.count() - : await jsonSchemaModel.count({}); - - if (schemaCount === 0) { - const fakeSchemas = Array.from({ length: 3 }, () => { - const schemaData = { - data: { - type: "object", - properties: { - name: { type: "string" }, - age: { type: "number" } - }, - required: ["name"] - }, - name: faker.internet.userName() - }; - return schemaData; - }); - - if (isSequelize) { - await jsonSchemaModel.bulkCreate(fakeSchemas); - } else { - await jsonSchemaModel.createEach(fakeSchemas).fetch(); - } - } -} +import { faker } from '@faker-js/faker'; +import { generate } from 'password-hash'; +import { DataSource } from 'typeorm'; +import { UserAP } from '../../src'; +import { Example } from '../models/sequelize/Example'; +export async function seedDatabase( + collections: Record, + count: number = 3 +) { + const getRandomTime = () => { + const hours = faker.number.int({ min: 0, max: 23 }).toString().padStart(2, '0'); + const minutes = faker.number.int({ min: 0, max: 59 }).toString().padStart(2, '0'); + return `${hours}:${minutes}`; + }; + + const exampleModel = collections.example ?? collections.Example; + const testModel = collections.test ?? collections.Test; + const userModel = collections.userap ?? collections.UserAP; + const groupModel = collections.groupap ?? collections.GroupAP; + + + const isSequelize = typeof exampleModel?.bulkCreate === 'function'; + + if (!exampleModel || !userModel || !groupModel || !isSequelize) { + throw new Error('Models should support the Sequelize interface'); + } + + // ------------------ Groups ------------------ // + const groupNames = [ + { name: 'Admins', description: 'System administrators', tokens: [ + 'ai-assistant-dummy', + 'ai-assistant-openai', + ] }, + { name: 'Users', description: 'Registered users', tokens: + [ + "create-test-model", + "read-test-model", + "update-test-model", + "delete-test-model", + + "create-example-model", + "read-example-model", + "update-example-model", + + "read-jsonschema-model", + "ai-assistant-dummy", + "ai-assistant-openai", + ] + }, + { name: 'Guests', description: 'Guest access' }, + ]; + + for (const group of groupNames) { + const exists = await groupModel.findOne({ where: { name: group.name } }); + + if (!exists) { + await groupModel.create(group); + } + } + + // ------------------ Users ------------------ // + const users = [ + { login: 'pass', password: 'pass', fullName: 'User Pass' }, + { login: 'demo', password: 'demo', fullName: 'Administrator', isAdministrator: true }, + { login: 'user1', password: 'user1', fullName: 'User One' }, + { login: 'admin', password: 'admin', fullName: 'Admin User', isAdministrator: true }, + { login: 'user2', password: 'user2', fullName: 'User Two' }, + { login: 'user3', password: 'user3', fullName: 'User Three', isConfirmed: true }, + { login: 'openai', password: 'openai', fullName: 'OpenAI Agent', isAdministrator: true, isConfirmed: true }, + ]; + + for (const u of users) { + const exists = await userModel.findOne({ where: { login: u.login } }); + + if (!exists) { + const userData = { + login: u.login, + password: u.password, + passwordHashed: generate(u.login+u.password+process.env.AP_PASSWORD_SALT), + fullName: u.fullName, + isActive: true, + isAdministrator: u.isAdministrator || false, + isConfirmed: u.isConfirmed + }; + + const userInstance = await userModel.create(userData); + + + // Linking to groups (simplified logic) + const groupName = u.isAdministrator ? 'Admins' : 'Users'; + + const group = await groupModel.findOne({ where: { name: groupName } }); + + if (group && group.addUser) { + await group.addUser(userInstance); + } + } + } + + + // ------------------ Example Records ------------------ // + const exampleCount = await exampleModel.count(); + + const allUsers: UserAP[] = await userModel.findAll(); + + if (exampleCount === 0) { + // Helper function to get ISO week number (YYYY-Www format) + const getWeekNumber = (date: Date): string => { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); + return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; + }; + + const fakeExamples = Array.from({ length: count }, () => { + const randomUser = faker.helpers.arrayElement(allUsers); + return { + title: faker.lorem.word(), + description: faker.lorem.paragraph(), + disabled_text: faker.lorem.sentence(), + sort: faker.datatype.boolean(), + time: getRandomTime(), + number: faker.number.int(300), + color: faker.color.rgb(), + range: faker.number.int({ min: 10, max: 80 }), + date: faker.date.past(), + month: faker.date.past().toISOString().slice(0, 7), + week: getWeekNumber(faker.date.past()), + datetime: faker.date.recent().toISOString().slice(0, 16), + code: faker.lorem.paragraphs(2), + editor: faker.lorem.paragraphs(3), + selectMany: faker.helpers.arrayElements(['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], 2), + select: faker.helpers.arrayElement(['decrease', 'increase', 'none']), + tui: faker.lorem.paragraphs(2), + datatable: [ + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) }, + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) } + ], + json: { key: faker.lorem.word(), value: faker.lorem.word(), count: faker.number.int(100) }, + ownerId: randomUser.id, + } + } + ); + + await exampleModel.bulkCreate(fakeExamples); + } + + // ------------------ Tests ------------------ // + const testCount = await testModel.count(); + + if (testCount === 0) { + const allUsers: UserAP[] = await userModel.findAll(); + + const allExamples: Example[] = await exampleModel.findAll(); + + const fakeTests = Array.from({ length: count }, () => { + const randomUser = faker.helpers.arrayElement(allUsers); + const randomExample = faker.helpers.arrayElement(allExamples); + + return { + title: faker.lorem.words(3), + ownerId: randomUser.id, + exampleId: randomExample?.id ?? null, + }; + }); + + await testModel.bulkCreate(fakeTests); + } + + + // ------------------ JsonSchemas ------------------ // + const jsonSchemaModel = collections.jsonschema ?? collections.JsonSchema; + + const schemaCount = await jsonSchemaModel.count(); + + if (schemaCount === 0) { + const fakeSchemas = Array.from({ length: 3 }, () => { + const schemaData = { + data: { + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" } + }, + required: ["name"] + }, + name: faker.internet.username() + }; + return schemaData; + }); + + await jsonSchemaModel.bulkCreate(fakeSchemas); + } +} + +export async function seedTypeOrmDatabase( + dataSource: DataSource, + count: number = 3 +) { + const getRandomTime = () => { + const hours = faker.number.int({ min: 0, max: 23 }).toString().padStart(2, '0'); + const minutes = faker.number.int({ min: 0, max: 59 }).toString().padStart(2, '0'); + return `${hours}:${minutes}`; + }; + + const groupRepository = dataSource.getRepository('GroupAP'); + const userRepository = dataSource.getRepository('UserAP'); + const exampleRepository = dataSource.getRepository('Example'); + const testRepository = dataSource.getRepository('Test'); + const jsonSchemaRepository = dataSource.getRepository('JsonSchema'); + + const groupNames = [ + { name: 'Admins', description: 'System administrators', tokens: [ + 'ai-assistant-dummy', + 'ai-assistant-openai', + ] }, + { name: 'Users', description: 'Registered users', tokens: + [ + "create-test-model", + "read-test-model", + "update-test-model", + "delete-test-model", + + "create-example-model", + "read-example-model", + "update-example-model", + + "read-jsonschema-model", + "ai-assistant-dummy", + "ai-assistant-openai", + ] + }, + { name: 'Guests', description: 'Guest access' }, + ]; + + for (const group of groupNames) { + const exists = await groupRepository.findOne({ where: { name: group.name } }); + if (!exists) { + await groupRepository.save(group); + } + } + + const users = [ + { login: 'pass', password: 'pass', fullName: 'User Pass' }, + { login: 'demo', password: 'demo', fullName: 'Administrator', isAdministrator: true }, + { login: 'user1', password: 'user1', fullName: 'User One' }, + { login: 'admin', password: 'admin', fullName: 'Admin User', isAdministrator: true }, + { login: 'user2', password: 'user2', fullName: 'User Two' }, + { login: 'user3', password: 'user3', fullName: 'User Three', isConfirmed: true }, + { login: 'openai', password: 'openai', fullName: 'OpenAI Agent', isAdministrator: true, isConfirmed: true }, + ]; + + for (const u of users) { + const exists = await userRepository.findOne({ where: { login: u.login } }); + if (exists) { + continue; + } + + const groupName = u.isAdministrator ? 'Admins' : 'Users'; + const group = await groupRepository.findOne({ where: { name: groupName } }); + await userRepository.save({ + login: u.login, + passwordHashed: generate(u.login + u.password + process.env.AP_PASSWORD_SALT), + fullName: u.fullName, + isActive: true, + isAdministrator: u.isAdministrator || false, + isConfirmed: u.isConfirmed, + groups: group ? [group] : [], + }); + } + + const exampleCount = await exampleRepository.count(); + const allUsers = await userRepository.find(); + + if (exampleCount === 0) { + const getWeekNumber = (date: Date): string => { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); + return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; + }; + + const fakeExamples = Array.from({ length: count }, () => { + const randomUser = faker.helpers.arrayElement(allUsers); + return { + title: faker.lorem.word(), + description: faker.lorem.paragraph(), + disabled_text: faker.lorem.sentence(), + sort: faker.datatype.boolean(), + time: getRandomTime(), + number: faker.number.int(300), + color: faker.color.rgb(), + range: faker.number.int({ min: 10, max: 80 }), + date: faker.date.past(), + month: faker.date.past().toISOString().slice(0, 7), + week: getWeekNumber(faker.date.past()), + datetime: faker.date.recent(), + code: faker.lorem.paragraphs(2), + editor: faker.lorem.paragraphs(3), + selectMany: faker.helpers.arrayElements(['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], 2), + select: faker.helpers.arrayElement(['decrease', 'increase', 'none']), + tui: faker.lorem.paragraphs(2), + datatable: [ + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) }, + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) } + ], + json: { key: faker.lorem.word(), value: faker.lorem.word(), count: faker.number.int(100) }, + owner: randomUser, + }; + }); + + await exampleRepository.save(fakeExamples); + } + + const testCount = await testRepository.count(); + + if (testCount === 0) { + const freshUsers = await userRepository.find(); + const allExamples = await exampleRepository.find(); + + const fakeTests = Array.from({ length: count }, () => { + const randomUser = faker.helpers.arrayElement(freshUsers); + const randomExample = faker.helpers.arrayElement(allExamples); + + return { + title: faker.lorem.words(3), + owner: randomUser, + example: randomExample ?? null, + }; + }); + + await testRepository.save(fakeTests); + } + + const schemaCount = await jsonSchemaRepository.count(); + + if (schemaCount === 0) { + const fakeSchemas = Array.from({ length: 3 }, () => ({ + data: { + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" } + }, + required: ["name"] + }, + name: faker.internet.username() + })); + + await jsonSchemaRepository.save(fakeSchemas); + } +} diff --git a/fixture/index.ts b/fixture/index.ts index 6027db50..45048f68 100644 --- a/fixture/index.ts +++ b/fixture/index.ts @@ -1,18 +1,11 @@ // Load environment variables from .env file import dotenv from 'dotenv'; dotenv.config(); +import "reflect-metadata"; -import { Adminizer } from "../dist"; +import { Adminizer, UserAP } from "../dist"; import { bindNavigation } from "../dist"; import http from 'http'; -import { WaterlineAdapter, WaterlineModel } from "../dist/lib/model/adapter/waterline"; -import Waterline from "waterline"; -import waterlineConfig from "./waterlineConfig"; -import ExampleWaterline from "./models/Example"; -import TestWaterline from "./models/Test"; -import TestCatalogWaterline from "./models/TestCatalog"; -import JsonSchemaWaterline from "./models/JsonSchema"; -import CategoryWaterline from "./models/Category"; import adminpanelConfig from "./adminizerConfig"; import { AdminpanelConfig } from "../dist/interfaces/adminpanelConfig"; import { sendNotificationsWithDelay } from "./helpers/notifications"; @@ -21,10 +14,8 @@ import cors from 'cors'; import { ReactQuill } from "../modules/controls/wysiwyg/ReactQuill"; -// Waterline imports -import { Sequelize } from "sequelize-typescript"; - // Sequelize imports +import { Sequelize } from "sequelize-typescript"; import fs from 'fs/promises'; import path from 'path'; import { Example as ExampleSequelize } from "./models/sequelize/Example"; @@ -33,7 +24,14 @@ import { Test as TestSequelize } from "./models/sequelize/Test"; import { Category as CategorySequelize } from "./models/sequelize/Category"; import { TestCatalog as TestCatalogSequelize } from "./models/sequelize/TestCatalog"; import { SequelizeAdapter } from "../dist/lib/model/adapter/sequelize"; -import { seedDatabase } from "./helpers/seedDatabase"; +import { DataSource } from "typeorm"; +import { TypeOrmAdapter } from "../dist/lib/model/adapter/typeorm"; +import { seedDatabase, seedTypeOrmDatabase } from "./helpers/seedDatabase"; +import { ExampleTypeOrm } from "./models/typeorm/Example"; +import { JsonSchemaTypeOrm } from "./models/typeorm/JsonSchema"; +import { TestTypeOrm } from "./models/typeorm/Test"; +import { CategoryTypeOrm } from "./models/typeorm/Category"; +import { TestCatalogTypeOrm } from "./models/typeorm/TestCatalog"; //Widgets imports @@ -43,6 +41,12 @@ import { InfoOne, Info4, Info3, InfoTwo } from "./widgets/Info"; import { CustomOne } from "./widgets/Custom"; import { ActionOne, ActionTwo } from "./widgets/Actions"; import { TestCatalog } from "./virtual-catalog/virtualCatalog"; +import { + ExampleDatatablePriceRangeFilterHandler, + ExampleJsonCustomFilterHandler, + TypeOrmExampleDatatablePriceRangeFilterHandler, + TypeOrmExampleJsonCustomFilterHandler +} from "./filters/customFilterHandlers"; import express from "express"; import cookieParser from "cookie-parser"; import { corsApi } from "./cors-api/api"; @@ -54,41 +58,12 @@ process.env.AP_PASSWORD_SALT = "FIXTURE" // Clean temp folder if (!process.env.NO_SEED_DATA || process.env.CLEAN_TMP) await cleanTempFolder(); process.env.JWT_SECRET = "fixture-jwt-secret" -// https://sailsjs.com/documentation/concepts/models-and-orm/standalone-waterline-usage const ormType = process.env.ORM ?? "sequelize"; let adminizer: Adminizer; -if (ormType === "waterline") { - const orm = new Waterline(); - await WaterlineAdapter.registerSystemModels(orm); - orm.registerModel(ExampleWaterline); - orm.registerModel(TestWaterline); - orm.registerModel(JsonSchemaWaterline); - orm.registerModel(CategoryWaterline); - orm.registerModel(TestCatalogWaterline); - - const ontology = await new Promise((resolve, reject) => { - orm.initialize(waterlineConfig as any, (err, ontology) => { - if (err) return reject(err); - resolve(ontology); - }); - }); - - const waterlineAdapter = new WaterlineAdapter({ orm, ontology }); - adminizer = new Adminizer([waterlineAdapter]); - await ormSharedFixtureLift(adminizer); - - if (!process.env.NO_SEED_DATA) { - try { - await seedDatabase(waterlineAdapter.models, 13); - console.log("Database seeded with random data!"); - } catch (seedErr) { - console.error("Error during database seeding:", seedErr); - } - } -} else { +if (ormType === "sequelize") { const tmpDir = path.join(process.cwd(), ".tmp"); const dbPath = path.join(tmpDir, "adminizer_fixture.sqlite"); const orm = new Sequelize({ @@ -115,6 +90,46 @@ if (ormType === "waterline") { console.error("Error during database seeding:", seedErr); } } + + // Enable debug logging + Adminizer.logger.level = 'debug'; +} else if (ormType === "typeorm") { + const tmpDir = path.join(process.cwd(), ".tmp"); + const dbPath = path.join(tmpDir, "adminizer_fixture_typeorm.sqlite"); + const systemEntities = await TypeOrmAdapter.loadSystemEntities(); + const dataSource = new DataSource({ + type: "sqlite", + database: dbPath, + entities: [ + ...systemEntities, + ExampleTypeOrm, + TestTypeOrm, + JsonSchemaTypeOrm, + CategoryTypeOrm, + TestCatalogTypeOrm, + ], + synchronize: process.env.ORM_ALTER !== "false", + logging: false, + }); + + await dataSource.initialize(); + + const typeOrmAdapter = new TypeOrmAdapter(dataSource); + adminizer = new Adminizer([typeOrmAdapter]); + await ormSharedFixtureLift(adminizer); + + if (!process.env.NO_SEED_DATA) { + try { + await seedTypeOrmDatabase(dataSource, 77); + console.log("TypeORM database seeded with random data!"); + } catch (seedErr) { + console.error("Error during TypeORM database seeding:", seedErr); + } + } + + Adminizer.logger.level = 'debug'; +} else { + throw new Error(`Unsupported fixture ORM "${ormType}". Supported ORM: sequelize, typeorm`); } // Finish @@ -136,18 +151,17 @@ async function cleanTempFolder() { } /** - * Shared method for all orm's + * Shared fixture setup * @param adminizer */ async function ormSharedFixtureLift(adminizer: Adminizer) { - let routePrefix = adminpanelConfig.routePrefix; process.env.ROUTE_PREFIX = adminpanelConfig.routePrefix; // Add custom module adminizer.emitter.on('adminizer:loaded', () => { - let policies: MiddlewareType[] = adminizer.config.policies; - const module = async (req: ReqType, res: ResType) => { - if (req.adminizer.config.auth.enable) { + let policies = adminizer.config.middlewares as MiddlewareType[]; + const module = async (req: ReqType, res: ResType, next: express.NextFunction) => { + if (req.adminizer.config.auth?.enable) { if (!req.user) { return res.redirect(`${req.adminizer.config.routePrefix}/model/userap/login`); } @@ -157,7 +171,7 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { const isDev = process.env.NODE_ENV === 'development'; const moduleComponent = isDev ? '/modules/test/ComponentB.tsx' : `${adminizer.config.routePrefix}/assets/modules/ComponentB.es.js`; - const users = await req.adminizer.modelHandler.model.get('userap')["_find"]({}) + const users = await req.adminizer.modelHandler.internal('auth').get('userap').find({}) return req.Inertia.render({ component: 'module', // required props: { @@ -172,13 +186,25 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { } - adminizer.app.get(`${adminizer.config.routePrefix}/module-test`, adminizer.policyManager.bindPolicies(policies, module)); - adminizer.app.post(`${adminizer.config.routePrefix}/module-test`, adminizer.policyManager.bindPolicies(policies, async (req: ReqType, res: ResType) => { - adminizer.sendNotification({ + adminizer.app.get(`${adminizer.config.routePrefix}/module-test`, adminizer.middlewareManager.bindMiddlewares(policies, module)); + adminizer.app.post(`${adminizer.config.routePrefix}/module-test`, adminizer.middlewareManager.bindMiddlewares(policies, async (req: ReqType, res: ResType) => { + const rawUserId = req.body.userId; + const userId = req.body.sendToAll + ? undefined + : Number(rawUserId); + + if (!req.body.sendToAll && (!Number.isInteger(userId) || userId <= 0)) { + return res.status(400).json({ + error: 'Invalid userId' + }); + } + + await adminizer.sendNotification({ title: "Test notification", message: req.body.message, notificationClass: 'general', - channel: '' + channel: '', + ...(userId ? { userId } : {}) }) res.json({ test: req.body @@ -187,9 +213,9 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { }); // add custom control wysiwyg - adminizer.emitter.on('adminizer:loaded', () => { - adminizer.controlsHandler.add(new ReactQuill(adminizer)) - }) + // adminizer.emitter.on('adminizer:loaded', () => { + // adminizer.controlsHandler.add(new ReactQuill(adminizer)) + // }) // Test cors adminizer.emitter.on('adminizer:loaded', () => { @@ -210,6 +236,14 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { await adminizer.init(adminpanelConfig as unknown as AdminpanelConfig) + if (ormType === "typeorm") { + adminizer.customFilterHandler.add(new TypeOrmExampleJsonCustomFilterHandler(), { force: true }); + adminizer.customFilterHandler.add(new TypeOrmExampleDatatablePriceRangeFilterHandler(), { force: true }); + } else { + adminizer.customFilterHandler.add(new ExampleJsonCustomFilterHandler(), { force: true }); + adminizer.customFilterHandler.add(new ExampleDatatablePriceRangeFilterHandler(), { force: true }); + } + if (adminizer.config.aiAssistant?.enabled) { // Dynamic import to avoid loading OpenAI dependencies when AI assistant is disabled const { OpenAiDataAgentService } = await import("./helpers/ai/OpenAiDataAgentService"); @@ -299,7 +333,10 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { // Custom route mainApp.get('/nav', async (req, res) => { try { - const header = await adminizer.modelHandler.model.get('navigationap')["_findOne"]({ label: 'header' }); + const header = await adminizer.modelHandler + .internal('navigation') + .get('navigationap') + .findOne({ where: { label: 'header' } }); res.json({ header: header }); } catch (error) { res.status(500).json({ error: 'Internal server error' }); @@ -340,7 +377,10 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { async function getNavigationSection(section: string): Promise { - const navigationRecord = await adminizer.modelHandler.model.get('navigationap')["_findOne"]({ label: section }); + const navigationRecord = await adminizer.modelHandler + .internal('navigation') + .get<{ tree?: NavTreeNode[] }>('navigationap') + .findOne({ where: { label: section } }); return navigationRecord?.tree ?? []; } diff --git a/fixture/locales/de.json b/fixture/locales/de.json index e69de29b..9e26dfee 100644 --- a/fixture/locales/de.json +++ b/fixture/locales/de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/fixture/locales/en.json b/fixture/locales/en.json index 0db05a6c..9e26dfee 100644 --- a/fixture/locales/en.json +++ b/fixture/locales/en.json @@ -1,88 +1 @@ -{ - "Log out": "Log out", - "Adminpanel": "Adminpanel", - "Website 1": "Website 1", - "Website 2 Website 2 Website 2": "Website 2 Website 2 Website 2", - "Website 3": "Website 3", - "Make read": "Make read", - "View All": "View All", - "Search": "Search", - "Make all read": "Make all read", - "Title": "Title", - "Message": "Message", - "Date": "Date", - "Diff": "Diff", - "The end of the list has been reached": "The end of the list has been reached", - "Login": "Login", - "Password": "Password", - "Welcome": "Welcome", - "Log in": "Log in", - "Register": "Register", - "Quick actions": "Quick actions", - "Toggle to change the grid. To save the grid, turn it off.": "Toggle to change the grid. To save the grid, turn it off.", - "You don't have any widgets selected yet. You can add them by clicking on the plus sign at the top right.": "You don't have any widgets selected yet. You can add them by clicking on the plus sign at the top right.", - "No widgets found": "No widgets found", - "All": "All", - "Custom": "Custom", - "Fast links": "Fast links", - "Actions": "Actions", - "Info": "Info", - "Switcher": "Switcher", - "Widget Switcher One": "Widget Switcher One", - "Site Switcher": "Site Switcher", - "Widget Switcher Two": "Widget Switcher Two", - "Switcher Two": "Switcher Two", - "Test": "Test", - "Add Test Model": "Add Test Model", - "example.com": "example.com", - "link to example.com": "link to example.com", - "Global Settings": "Global Settings", - "Info widget One": "Info widget One", - "Info One": "Info One", - "Info widget Two": "Info widget Two", - "Info Two": "Info Two", - "Info widget 3": "Info widget 3", - "Info 31111": "Info 31111", - "Info widget 4": "Info widget 4", - "Info 4": "Info 4", - "Action One": "Action One", - "Action Two": "Action Two", - "Widget Custom One": "Widget Custom One", - "Site Custom": "Site Custom", - "Yes": "Yes", - "No": "No", - "Are you sure?": "Are you sure?", - "No records found !": "No records found !", - "Reset": "Reset", - "create": "create", - "Edit": "Edit", - "View": "View", - "Delete": "Delete", - "User login": "User login", - "Full Name": "Full Name", - "avatar": "avatar", - "expires": "expires", - "is administrator": "is administrator", - "widgets": "widgets", - "isConfirmed": "isConfirmed", - "id": "id", - "example": "example", - "owner": "owner", - "userAPs": "userAPs", - "Not found": "Not found", - "Back": "Back", - "Save": "Save", - "Pretend to be a user": "Pretend to be a user", - "Values in \"password\" and \"repeatPassword\" fields should be the same": "Values in \"password\" and \"repeatPassword\" fields should be the same", - "User settings": "User settings", - "User groups": "User groups", - "Full name": "Full name", - "E-mail": "E-mail", - "Timezone": "Timezone", - "Locale": "Locale", - "Profile expires": "Profile expires", - "Is Administrator": "Is Administrator", - "Is confirmed": "Is confirmed", - "Leave this field empty if you don't want to change the password": "Leave this field empty if you don't want to change the password", - "Repeat password": "Repeat password" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/fixture/locales/ru.json b/fixture/locales/ru.json index 165631ee..4eb7c3f9 100644 --- a/fixture/locales/ru.json +++ b/fixture/locales/ru.json @@ -1,58 +1,3 @@ { - "Actions": "Actions", - "Yes": "Yes", - "No": "No", - "Are you sure?": "Are you sure?", - "No records found !": "No records found !", - "Search": "Search", - "Reset": "Reset", - "create": "создать", - "Edit": "Edit", - "View": "View", - "Delete": "Delete", - "User login": "User login", - "Full Name": "Full Name", - "avatar": "avatar", - "expires": "expires", - "is administrator": "is administrator", - "widgets": "widgets", - "isConfirmed": "isConfirmed", - "Make read": "Make read", - "View All": "View All", - "Make all read": "Make all read", - "Title": "Title", - "Message": "Message", - "Date": "Date", - "Diff": "Diff", - "The end of the list has been reached": "The end of the list has been reached", - "Quick actions": "Quick actions", - "Toggle to change the grid. To save the grid, turn it off.": "Toggle to change the grid. To save the grid, turn it off.", - "You don't have any widgets selected yet. You can add them by clicking on the plus sign at the top right.": "You don't have any widgets selected yet. You can add them by clicking on the plus sign at the top right.", - "No widgets found": "No widgets found", - "All": "All", - "Custom": "Custom", - "Fast links": "Fast links", - "Info": "Info", - "Switcher": "Switcher", - "Widget Switcher One": "Widget Switcher One", - "Site Switcher": "Site Switcher", - "Widget Switcher Two": "Widget Switcher Two", - "Switcher Two": "Switcher Two", - "Test": "Test", - "Add Test Model": "Add Test Model", - "example.com": "example.com", - "link to example.com": "link to example.com", - "Global Settings": "Global Settings", - "Info widget One": "Info widget One", - "Info One": "Info One", - "Info widget Two": "Info widget Two", - "Info Two": "Info Two", - "Info widget 3": "Info widget 3", - "Info 31111": "Info 31111", - "Info widget 4": "Info widget 4", - "Info 4": "Info 4", - "Action One": "Action One", - "Action Two": "Action Two", - "Widget Custom One": "Widget Custom One", - "Site Custom": "Site Custom" + } \ No newline at end of file diff --git a/fixture/locales/ua.json b/fixture/locales/ua.json index e69de29b..9e26dfee 100644 --- a/fixture/locales/ua.json +++ b/fixture/locales/ua.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/fixture/locales_missing/en.json b/fixture/locales_missing/en.json new file mode 100644 index 00000000..fe3bcb25 --- /dev/null +++ b/fixture/locales_missing/en.json @@ -0,0 +1,51 @@ +{ + "Diff": "Diff", + "Preview": "Preview", + "On": "On", + "Off": "Off", + "No notifications found": "No notifications found", + "No notifications": "No notifications", + "Changes not found": "Changes not found", + "Old": "Old", + "New": "New", + "Added": "Added", + "Removed": "Removed", + "Updated": "Updated", + "changed type": "changed type", + "Error: Invalid field data": "Error: Invalid field data", + "Error: Fields data is invalid": "Error: Fields data is invalid", + "No fields to display": "No fields to display", + "Error: Some fields are missing required properties (name, type, label)": "Error: Some fields are missing required properties (name, type, label)", + "Visible": "Visible", + "First": "First", + "Last": "Last", + "Previous": "Previous", + "Next": "Next", + "Send feedback": "Send feedback", + "Feedback": "Feedback", + "Attach files": "Attach files", + "Send": "Send", + "Sending...": "Sending...", + "Feedback sent successfully": "Feedback sent successfully", + "Title is required": "Title is required", + "File size exceeds the 5 MB limit": "File size exceeds the 5 MB limit", + "Remove file": "Remove file", + "Max 5 MB per file": "Max 5 MB per file", + "View All": "View All", + "Make read": "Make read", + "Make all read": "Make all read", + "Message": "Message", + "User Filters": "User Filters", + "System": "System", + "Platform": "Platform", + "Test Module": "Test Module", + "Nav Header": "Nav Header", + "Nav Footer": "Nav Footer", + "Groups": "Groups", + "Test model": "Test model", + "All controls": "All controls", + "Json schema": "Json schema", + "Категории": "Категории", + "apiKey": "apiKey", + "General": "General" +} \ No newline at end of file diff --git a/fixture/locales_missing/ru.json b/fixture/locales_missing/ru.json new file mode 100644 index 00000000..b7e7b627 --- /dev/null +++ b/fixture/locales_missing/ru.json @@ -0,0 +1,40 @@ +{ + "Diff": "Diff", + "Preview": "Preview", + "On": "On", + "Off": "Off", + "No notifications found": "No notifications found", + "No notifications": "No notifications", + "Changes not found": "Changes not found", + "Old": "Old", + "New": "New", + "Added": "Added", + "Removed": "Removed", + "Updated": "Updated", + "changed type": "changed type", + "Error: Invalid field data": "Error: Invalid field data", + "Error: Fields data is invalid": "Error: Fields data is invalid", + "No fields to display": "No fields to display", + "Error: Some fields are missing required properties (name, type, label)": "Error: Some fields are missing required properties (name, type, label)", + "Visible": "Visible", + "View All": "View All", + "Make read": "Make read", + "Make all read": "Make all read", + "Message": "Message", + "Platform": "Platform", + "Test Module": "Test Module", + "Nav Header": "Nav Header", + "Nav Footer": "Nav Footer", + "Test Catalog": "Test Catalog", + "Test model": "Test model", + "All controls": "All controls", + "Json schema": "Json schema", + "Категории": "Категории", + "Log out": "Log out", + "Adminpanel": "Adminpanel", + "Website 1": "Website 1", + "Website 2 Website 2 Website 2": "Website 2 Website 2 Website 2", + "Website 3": "Website 3", + "apiKey": "apiKey", + "General": "General" +} \ No newline at end of file diff --git a/fixture/models/Category.ts b/fixture/models/Category.ts deleted file mode 100644 index 5f30be91..00000000 --- a/fixture/models/Category.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Waterline from "waterline"; - -const Category = Waterline.Collection.extend({ - identity: "category", - datastore: "default", - primaryKey: "id", - attributes: { - // @ts-ignore - id: {type: "number", autoMigrations: {autoIncrement: true}}, - title: { - type: "string" - }, - slug: { - type: "string" - }, - mediamanager_one: { - type: "json" - }, - mediamanager_two: { - type: "json" - }, - single_file: { - type: "json" - } - } -}); - -export default Category; diff --git a/fixture/models/Example.ts b/fixture/models/Example.ts deleted file mode 100644 index 27ed1897..00000000 --- a/fixture/models/Example.ts +++ /dev/null @@ -1,41 +0,0 @@ -import Waterline from "waterline"; - -const Example = Waterline.Collection.extend({ - identity: "example", - datastore: "default", - primaryKey: "id", - attributes: { - // @ts-ignore - id: { type: "number", autoMigrations: { autoIncrement: true } }, - title: { type: "string" }, - description: { type: "string"}, - disabled_text: { type: "string"}, - sort: { type: "boolean" }, - time: { type: "string" }, - number: { type: "number" }, - color: { type: "string" }, - range: { type: "string" }, - date: { type: "string" }, - month: { type: "string" }, - week: { type: "string" }, - code: { type: "string" }, - editor: { type: "string" }, - selectMany: { type: "json" }, - select: { type: "string" }, - testRelation: { model: "Example" }, - tui: { type: "string" }, - datatable: { type: "json" }, - json: { type: "json" }, - tests: { collection: "Test", via: "example" }, - datetime: { type: "string" }, - geojson: { type: "json" }, - - // Many-to-many relation with UserAP - // @ts-ignore - owner: { - model: 'UserAP' - }, - } -}); - -export default Example; diff --git a/fixture/models/JsonSchema.ts b/fixture/models/JsonSchema.ts deleted file mode 100644 index 58de6b22..00000000 --- a/fixture/models/JsonSchema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Waterline from "waterline"; - -const JsonSchema = Waterline.Collection.extend({ - identity: "jsonschema", - datastore: "default", - primaryKey: "id", - attributes: { - // @ts-ignore - id: { type: "number", autoMigrations: { autoIncrement: true } }, - data: { type: "json" }, - data2: { type: "json" }, - } -}); - -export default JsonSchema; diff --git a/fixture/models/Test.ts b/fixture/models/Test.ts deleted file mode 100644 index c244c9a9..00000000 --- a/fixture/models/Test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import Waterline from "waterline"; - -const Test = Waterline.Collection.extend({ - identity: "test", - datastore: "default", - primaryKey: "id", - attributes: { - // @ts-ignore - id: { type: "number", autoMigrations: { autoIncrement: true } }, - title: { type: "string", required: true }, - example: { model: "example" }, - - owner: { - model: 'UserAP' - }, - // Many-to-many relation with UserAP - // @ts-ignore - userAPs: { - collection: "UserAP" - } - } -}); - -export default Test; \ No newline at end of file diff --git a/fixture/models/TestCatalog.ts b/fixture/models/TestCatalog.ts deleted file mode 100644 index cd2dad98..00000000 --- a/fixture/models/TestCatalog.ts +++ /dev/null @@ -1,14 +0,0 @@ -import Waterline from "waterline"; - -const TestCatalog = Waterline.Collection.extend({ - identity: "testcatalog", - datastore: "default", - primaryKey: "id", - attributes: { - // @ts-ignore - id: { type: "number", autoMigrations: { autoIncrement: true } }, - tree: { type: "json" }, - } -}); - -export default TestCatalog; diff --git a/fixture/models/sequelize/Example.ts b/fixture/models/sequelize/Example.ts index 710572a5..5e4db3c1 100644 --- a/fixture/models/sequelize/Example.ts +++ b/fixture/models/sequelize/Example.ts @@ -39,62 +39,62 @@ import {UserAP} from "../../../src"; @Column(DataType.BOOLEAN) declare sort: boolean; - - @Column(DataType.STRING) + + @Column(DataType.TIME) declare time: string; - + @Column(DataType.INTEGER) declare number: number; - + @Column(DataType.STRING) declare color: string; - - @Column(DataType.STRING) - declare range: string; - - @Column(DataType.STRING) - declare date: string; - + + @Column(DataType.INTEGER) + declare range: number; + + @Column(DataType.DATEONLY) + declare date: Date; + @Column(DataType.STRING) declare month: string; - + @Column(DataType.STRING) declare week: string; - + @Column(DataType.STRING) declare code: string; - + @Column(DataType.STRING) declare editor: string; - + @Column(DataType.JSON) declare selectMany: object; - + @Column(DataType.STRING) declare select: string; - + @ForeignKey(() => Example) @Column(DataType.INTEGER) declare testRelation: number; - + @BelongsTo(() => Example) declare testRelationExample?: Example; - + @Column(DataType.STRING) declare tui: string; - + @Column(DataType.JSON) declare datatable: object; - + @Column(DataType.JSON) declare json: object; - + @HasMany(() => Test, 'exampleId') declare tests: Test[]; - - @Column(DataType.STRING) - declare datetime: string; - + + @Column(DataType.DATE) + declare datetime: Date; + @Column(DataType.JSON) declare geojson: object; diff --git a/fixture/models/typeorm/Category.ts b/fixture/models/typeorm/Category.ts new file mode 100644 index 00000000..78f112bc --- /dev/null +++ b/fixture/models/typeorm/Category.ts @@ -0,0 +1,52 @@ +import { EntitySchema } from "typeorm"; + +export interface Category { + id: string; + title?: string; + slug?: string; + mediamanager_one?: object; + mediamanager_two?: object; + single_file?: object; + createdAt?: Date; + updatedAt?: Date; +} + +export const CategoryTypeOrm = new EntitySchema({ + name: "Category", + tableName: "category", + columns: { + id: { + type: "varchar", + primary: true, + generated: "uuid", + }, + title: { + type: "varchar", + nullable: true, + }, + slug: { + type: "varchar", + nullable: true, + }, + mediamanager_one: { + type: "simple-json", + nullable: true, + }, + mediamanager_two: { + type: "simple-json", + nullable: true, + }, + single_file: { + type: "simple-json", + nullable: true, + }, + createdAt: { + type: "datetime", + createDate: true, + }, + updatedAt: { + type: "datetime", + updateDate: true, + }, + }, +}); diff --git a/fixture/models/typeorm/Example.ts b/fixture/models/typeorm/Example.ts new file mode 100644 index 00000000..e6f19c4c --- /dev/null +++ b/fixture/models/typeorm/Example.ts @@ -0,0 +1,155 @@ +import { EntitySchema } from "typeorm"; +import type { Test } from "./Test"; + +export interface Example { + id: string; + title?: string; + description?: string; + disabled_text?: string; + sort?: boolean; + time?: string; + number?: number; + color?: string; + range?: number; + date?: Date; + month?: string; + week?: string; + code?: string; + editor?: string; + selectMany?: object; + select?: string; + testRelation?: Example; + testRelationExample?: Example; + tui?: string; + datatable?: object; + json?: object; + tests?: Test[]; + datetime?: Date; + geojson?: object; + owner?: object; + createdAt?: Date; + updatedAt?: Date; +} + +export const ExampleTypeOrm = new EntitySchema({ + name: "Example", + tableName: "example", + columns: { + id: { + type: "varchar", + primary: true, + generated: "uuid", + }, + title: { + type: "varchar", + nullable: true, + }, + description: { + type: "varchar", + nullable: true, + }, + disabled_text: { + type: "varchar", + nullable: true, + }, + sort: { + type: "boolean", + nullable: true, + }, + time: { + type: "varchar", + nullable: true, + }, + number: { + type: "integer", + nullable: true, + }, + color: { + type: "varchar", + nullable: true, + }, + range: { + type: "integer", + nullable: true, + }, + date: { + type: "datetime", + nullable: true, + }, + month: { + type: "varchar", + nullable: true, + }, + week: { + type: "varchar", + nullable: true, + }, + code: { + type: "varchar", + nullable: true, + }, + editor: { + type: "varchar", + nullable: true, + }, + selectMany: { + type: "simple-json", + nullable: true, + }, + select: { + type: "varchar", + nullable: true, + }, + tui: { + type: "varchar", + nullable: true, + }, + datatable: { + type: "simple-json", + nullable: true, + }, + json: { + type: "simple-json", + nullable: true, + }, + datetime: { + type: "datetime", + nullable: true, + }, + geojson: { + type: "simple-json", + nullable: true, + }, + createdAt: { + type: "datetime", + createDate: true, + }, + updatedAt: { + type: "datetime", + updateDate: true, + }, + }, + relations: { + testRelationExample: { + type: "many-to-one", + target: "Example", + joinColumn: { + name: "testRelationId", + }, + nullable: true, + }, + tests: { + type: "one-to-many", + target: "Test", + inverseSide: "example", + }, + owner: { + type: "many-to-one", + target: "UserAP", + joinColumn: { + name: "ownerId", + }, + nullable: true, + }, + }, +}); diff --git a/fixture/models/typeorm/JsonSchema.ts b/fixture/models/typeorm/JsonSchema.ts new file mode 100644 index 00000000..11e72144 --- /dev/null +++ b/fixture/models/typeorm/JsonSchema.ts @@ -0,0 +1,37 @@ +import { EntitySchema } from "typeorm"; + +export interface JsonSchemaRecord { + id: string; + name: string; + data?: object; + createdAt?: Date; + updatedAt?: Date; +} + +export const JsonSchemaTypeOrm = new EntitySchema({ + name: "JsonSchema", + tableName: "jsonschema", + columns: { + id: { + type: "varchar", + primary: true, + generated: "uuid", + }, + name: { + type: "varchar", + nullable: false, + }, + data: { + type: "simple-json", + nullable: true, + }, + createdAt: { + type: "datetime", + createDate: true, + }, + updatedAt: { + type: "datetime", + updateDate: true, + }, + }, +}); diff --git a/fixture/models/typeorm/Test.ts b/fixture/models/typeorm/Test.ts new file mode 100644 index 00000000..27cdd177 --- /dev/null +++ b/fixture/models/typeorm/Test.ts @@ -0,0 +1,83 @@ +import { EntitySchema } from "typeorm"; +import type { Example } from "./Example"; +import type { JsonSchemaRecord } from "./JsonSchema"; + +export interface Test { + id: string; + title: string; + mediamanager?: object; + example?: Example; + schema?: JsonSchemaRecord; + owner?: object; + userAPs?: object[]; + createdAt?: Date; + updatedAt?: Date; +} + +export const TestTypeOrm = new EntitySchema({ + name: "Test", + tableName: "test", + columns: { + id: { + type: "varchar", + primary: true, + generated: "uuid", + }, + title: { + type: "varchar", + nullable: false, + }, + mediamanager: { + type: "simple-json", + nullable: true, + }, + createdAt: { + type: "datetime", + createDate: true, + }, + updatedAt: { + type: "datetime", + updateDate: true, + }, + }, + relations: { + example: { + type: "many-to-one", + target: "Example", + inverseSide: "tests", + joinColumn: { + name: "exampleId", + }, + nullable: true, + }, + schema: { + type: "many-to-one", + target: "JsonSchema", + joinColumn: { + name: "schemaId", + }, + nullable: true, + }, + owner: { + type: "many-to-one", + target: "UserAP", + joinColumn: { + name: "ownerId", + }, + nullable: true, + }, + userAPs: { + type: "many-to-many", + target: "UserAP", + joinTable: { + name: "test_useraps", + joinColumn: { + name: "testId", + }, + inverseJoinColumn: { + name: "userAPId", + }, + }, + }, + }, +}); diff --git a/fixture/models/typeorm/TestCatalog.ts b/fixture/models/typeorm/TestCatalog.ts new file mode 100644 index 00000000..cc482e14 --- /dev/null +++ b/fixture/models/typeorm/TestCatalog.ts @@ -0,0 +1,22 @@ +import { EntitySchema } from "typeorm"; + +export interface TestCatalog { + id: string; + tree?: object; +} + +export const TestCatalogTypeOrm = new EntitySchema({ + name: "TestCatalog", + tableName: "testcatalog", + columns: { + id: { + type: "varchar", + primary: true, + generated: "uuid", + }, + tree: { + type: "simple-json", + nullable: true, + }, + }, +}); diff --git a/fixture/tsconfig.json b/fixture/tsconfig.json index 64cddf55..371bae59 100644 --- a/fixture/tsconfig.json +++ b/fixture/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "module": "ESNext", "target": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", "esModuleInterop": true, "sourceMap": false, "declaration": true, @@ -10,6 +10,9 @@ "skipLibCheck": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, + "strict": false, + "strictNullChecks": false, + "useUnknownInCatchVariables": false, "alwaysStrict": true, "noImplicitAny": true, "noImplicitReturns": true, diff --git a/fixture/virtual-catalog/virtualCatalog.ts b/fixture/virtual-catalog/virtualCatalog.ts index c37cbf35..2bbfa812 100644 --- a/fixture/virtual-catalog/virtualCatalog.ts +++ b/fixture/virtual-catalog/virtualCatalog.ts @@ -25,16 +25,20 @@ class TestCatalogStorageService { } protected async initModel() { - // Direct call by model adapter - const model = await this.adminizer.modelHandler.model.get(this.model)["_findOne"]({}); + const model = await this.adminizer.modelHandler + .internal("test-catalog") + .get(this.model) + .findOne({}); if (model) { Adminizer.log.info(`Found existing text catalog model`); await this.populateFromTree(model.tree); } else { const tree = {tree: [] as any}; - // Direct call by model adapter - await this.adminizer.modelHandler.model.get(this.model)["_create"](tree); + await this.adminizer.modelHandler + .internal("test-catalog") + .get(this.model) + .create(tree); Adminizer.log.info(`Created a new test catalog model`); } } @@ -120,11 +124,10 @@ class TestCatalogStorageService { public async saveToDB() { let tree = await this.buildTree() try { - // Direct call by model adapter - await this.adminizer.modelHandler.model.get(this.model)["_updateOne"]( - {}, - {tree: tree} - ) + await this.adminizer.modelHandler + .internal("test-catalog") + .get(this.model) + .updateOne({}, {tree: tree}) } catch (e) { console.log(e) throw 'test catalog model update error' @@ -270,7 +273,7 @@ export class TestGroup extends AbstractGroup { return Promise.resolve({ type: type, data: { - path: process.env.VITE_ENV === 'dev' ? '/modules/testCatalog/group.tsx' : `${this.adminizer.config.routePrefix}/assets/modules/Group.es.js` + path: process.env.ADMINIZER_ENV === 'dev' ? '/modules/testCatalog/group.tsx' : `${this.adminizer.config.routePrefix}/assets/modules/Group.es.js` } }) } @@ -285,7 +288,7 @@ export class TestGroup extends AbstractGroup { type: type, data: { item: item, - path: process.env.VITE_ENV === 'dev' ? '/modules/testCatalog/group.tsx' : `${req.adminizer.config.routePrefix}/assets/modules/Group.es.js` + path: process.env.ADMINIZER_ENV === 'dev' ? '/modules/testCatalog/group.tsx' : `${req.adminizer.config.routePrefix}/assets/modules/Group.es.js` } }) } @@ -338,8 +341,10 @@ class TestItemM extends AbstractItem { let storage = StorageHandler.getStorage() let storageData = null if (data._method === 'select') { - // Direct call by model adapter - let record = await this.adminizer.modelHandler.model.get(this.model)["_findOne"]({id: data.record}) + let record = await this.adminizer.modelHandler + .internal("test-catalog") + .get(this.model) + .findOne({where: {id: data.record}}) storageData = await this.dataPreparation({ record: record, parentId: data.parentId, @@ -378,8 +383,10 @@ class TestItemM extends AbstractItem { } }> { let type: 'model.link' = 'model.link' - // Direct call by model adapter - let itemsDB = await this.adminizer.modelHandler.model.get(this.model)["_find"]({}) + let itemsDB = await this.adminizer.modelHandler + .internal("test-catalog") + .get(this.model) + .find({}) let items = itemsDB.map((item: any) => { return { id: item.id, @@ -597,7 +604,7 @@ export class ExternalAction extends ActionHandler { public readonly selectedItemTypes: string[] = [] getPopUpTemplate(req?: ReqType): Promise { - return Promise.resolve(process.env.VITE_ENV === 'dev' ? '/modules/testCatalog/action.tsx' : `${req.adminizer.config.routePrefix}/assets/modules/catalogAction.es.js`); + return Promise.resolve(process.env.ADMINIZER_ENV === 'dev' ? '/modules/testCatalog/action.tsx' : `${req.adminizer.config.routePrefix}/assets/modules/catalogAction.es.js`); } getLink(data?: any): Promise { diff --git a/fixture/waterlineConfig.ts b/fixture/waterlineConfig.ts deleted file mode 100644 index 195ac099..00000000 --- a/fixture/waterlineConfig.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Config } from "waterline"; -// @ts-ignore -import sailsDisk from "sails-disk"; -// import sailsPostgresql from "sails-postgresql"; - -const config: Config = { - adapters: { - disk: sailsDisk, - // postgres: sailsPostgresql - }, - datastores: { - default: { - adapter: "disk", - // @ts-ignore - inMemoryOnly: false, - // adapter: "postgres", - // // @ts-ignore - // url: "postgresql://user:password@localhost:5432/mydatabase", - // ssl: false - } - } -}; - -export default config; diff --git a/fixture/widgets/Links.ts b/fixture/widgets/Links.ts index 3c7cf9b4..ac27707e 100644 --- a/fixture/widgets/Links.ts +++ b/fixture/widgets/Links.ts @@ -26,14 +26,6 @@ export class SiteLinks extends LinkBase { backgroundCSS: null, linkType: "blank" }, - { - name: 'Global Settings', - description: 'Global Settings', - link: '/adminizer/form/global', - icon: 'polyline', - backgroundCSS: null, - linkType: "self" - } ]; getLinks(): Promise { diff --git a/helpers.txt b/helpers.txt deleted file mode 100644 index 3f5a0920..00000000 --- a/helpers.txt +++ /dev/null @@ -1,2 +0,0 @@ -DROP TABLE category -DROP TABLE historyactionsap \ No newline at end of file diff --git a/modules/controls/wysiwyg/ReactQuill.ts b/modules/controls/wysiwyg/ReactQuill.ts index 2dbec889..327f84c4 100644 --- a/modules/controls/wysiwyg/ReactQuill.ts +++ b/modules/controls/wysiwyg/ReactQuill.ts @@ -23,7 +23,7 @@ export class ReactQuill extends AbstractControls { } getJsPath(): string { - if (process.env.VITE_ENV === 'dev') { + if (process.env.ADMINIZER_ENV === 'dev') { return this.path.jsPath.dev; } else { return this.path.jsPath.production diff --git a/modules/test/ComponentB.es.js b/modules/test/ComponentB.es.js index ef4b983b..c49a48bd 100644 --- a/modules/test/ComponentB.es.js +++ b/modules/test/ComponentB.es.js @@ -1,115 +1,15747 @@ -var x = { exports: {} }, d = {}; -/** - * @license React - * react-jsx-runtime.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -var w; -function R() { - if (w) return d; - w = 1; - var r = Symbol.for("react.transitional.element"), i = Symbol.for("react.fragment"); - function c(a, t, n) { - var l = null; - if (n !== void 0 && (l = "" + n), t.key !== void 0 && (l = "" + t.key), "key" in t) { +var nf = { exports: {} }, Ai = {}; +var Zm; +function Gv() { + if (Zm) return Ai; + Zm = 1; + var l = /* @__PURE__ */ Symbol.for("react.transitional.element"), i = /* @__PURE__ */ Symbol.for("react.fragment"); + function s(c, o, p) { + var d = null; + if (p !== void 0 && (d = "" + p), o.key !== void 0 && (d = "" + o.key), "key" in o) { + p = {}; + for (var m in o) + m !== "key" && (p[m] = o[m]); + } else p = o; + return o = p.ref, { + $$typeof: l, + type: c, + key: d, + ref: o !== void 0 ? o : null, + props: p + }; + } + return Ai.Fragment = i, Ai.jsx = s, Ai.jsxs = s, Ai; +} +var Vm; +function Zv() { + return Vm || (Vm = 1, nf.exports = Gv()), nf.exports; +} +var Ge = Zv(); +function Vv(l) { + return typeof l == "symbol" || l instanceof Symbol; +} +const Km = typeof globalThis == "object" && globalThis || typeof window == "object" && window || typeof self == "object" && self || typeof global == "object" && global || /* @__PURE__ */ (function() { + return this; +})() || Function("return this")(); +function Kv() { +} +function Pv(l) { + return l == null || typeof l != "object" && typeof l != "function"; +} +function Jv(l) { + return ArrayBuffer.isView(l) && !(l instanceof DataView); +} +function vf(l) { + return Object.getOwnPropertySymbols(l).filter((i) => Object.prototype.propertyIsEnumerable.call(l, i)); +} +function Es(l) { + return l == null ? l === void 0 ? "[object Undefined]" : "[object Null]" : Object.prototype.toString.call(l); +} +const Np = "[object RegExp]", qp = "[object String]", jp = "[object Number]", Hp = "[object Boolean]", Sf = "[object Arguments]", Bp = "[object Symbol]", _p = "[object Date]", Cp = "[object Map]", Lp = "[object Set]", Xp = "[object Array]", Fv = "[object Function]", Yp = "[object ArrayBuffer]", hs = "[object Object]", $v = "[object Error]", Qp = "[object DataView]", Gp = "[object Uint8Array]", Zp = "[object Uint8ClampedArray]", Vp = "[object Uint16Array]", Kp = "[object Uint32Array]", Wv = "[object BigUint64Array]", Pp = "[object Int8Array]", Jp = "[object Int16Array]", Fp = "[object Int32Array]", kv = "[object BigInt64Array]", $p = "[object Float32Array]", Wp = "[object Float64Array]"; +function bf(l) { + return typeof Km.Buffer < "u" && Km.Buffer.isBuffer(l); +} +function za(l, i, s, c = /* @__PURE__ */ new Map(), o = void 0) { + const p = o?.(l, i, s, c); + if (p !== void 0) + return p; + if (Pv(l)) + return l; + if (c.has(l)) + return c.get(l); + if (Array.isArray(l)) { + const d = new Array(l.length); + c.set(l, d); + for (let m = 0; m < l.length; m++) + d[m] = za(l[m], m, s, c, o); + return Object.hasOwn(l, "index") && (d.index = l.index), Object.hasOwn(l, "input") && (d.input = l.input), d; + } + if (l instanceof Date) + return new Date(l.getTime()); + if (l instanceof RegExp) { + const d = new RegExp(l.source, l.flags); + return d.lastIndex = l.lastIndex, d; + } + if (l instanceof Map) { + const d = /* @__PURE__ */ new Map(); + c.set(l, d); + for (const [m, g] of l) + d.set(m, za(g, m, s, c, o)); + return d; + } + if (l instanceof Set) { + const d = /* @__PURE__ */ new Set(); + c.set(l, d); + for (const m of l) + d.add(za(m, void 0, s, c, o)); + return d; + } + if (bf(l)) + return l.subarray(); + if (Jv(l)) { + const d = new (Object.getPrototypeOf(l)).constructor(l.length); + c.set(l, d); + for (let m = 0; m < l.length; m++) + d[m] = za(l[m], m, s, c, o); + return d; + } + if (l instanceof ArrayBuffer || typeof SharedArrayBuffer < "u" && l instanceof SharedArrayBuffer) + return l.slice(0); + if (l instanceof DataView) { + const d = new DataView(l.buffer.slice(0), l.byteOffset, l.byteLength); + return c.set(l, d), cl(d, l, s, c, o), d; + } + if (typeof File < "u" && l instanceof File) { + const d = new File([l], l.name, { + type: l.type + }); + return c.set(l, d), cl(d, l, s, c, o), d; + } + if (typeof Blob < "u" && l instanceof Blob) { + const d = new Blob([l], { type: l.type }); + return c.set(l, d), cl(d, l, s, c, o), d; + } + if (l instanceof Error) { + const d = structuredClone(l); + return c.set(l, d), d.message = l.message, d.name = l.name, d.stack = l.stack, d.cause = l.cause, d.constructor = l.constructor, cl(d, l, s, c, o), d; + } + if (l instanceof Boolean) { + const d = new Boolean(l.valueOf()); + return c.set(l, d), cl(d, l, s, c, o), d; + } + if (l instanceof Number) { + const d = new Number(l.valueOf()); + return c.set(l, d), cl(d, l, s, c, o), d; + } + if (l instanceof String) { + const d = new String(l.valueOf()); + return c.set(l, d), cl(d, l, s, c, o), d; + } + if (typeof l == "object" && Iv(l)) { + const d = Object.create(Object.getPrototypeOf(l)); + return c.set(l, d), cl(d, l, s, c, o), d; + } + return l; +} +function cl(l, i, s = l, c, o) { + const p = [...Object.keys(i), ...vf(i)]; + for (let d = 0; d < p.length; d++) { + const m = p[d], g = Object.getOwnPropertyDescriptor(l, m); + (g == null || g.writable) && (l[m] = za(i[m], m, s, c, o)); + } +} +function Iv(l) { + switch (Es(l)) { + case Sf: + case Xp: + case Yp: + case Qp: + case Hp: + case _p: + case $p: + case Wp: + case Pp: + case Jp: + case Fp: + case Cp: + case jp: + case hs: + case Np: + case Lp: + case qp: + case Bp: + case Gp: + case Zp: + case Vp: + case Kp: + return !0; + default: + return !1; + } +} +function sn(l) { + return za(l, void 0, l, /* @__PURE__ */ new Map(), void 0); +} +function Pm(l) { + if (!l || typeof l != "object") + return !1; + const i = Object.getPrototypeOf(l); + return i === null || i === Object.prototype || Object.getPrototypeOf(i) === null ? Object.prototype.toString.call(l) === "[object Object]" : !1; +} +function ws(l) { + return l === "__proto__"; +} +function kp(l, i) { + return l === i || Number.isNaN(l) && Number.isNaN(i); +} +function eS(l, i, s) { + return Ni(l, i, void 0, void 0, void 0, void 0, s); +} +function Ni(l, i, s, c, o, p, d) { + const m = d(l, i, s, c, o, p); + if (m !== void 0) + return m; + if (typeof l == typeof i) + switch (typeof l) { + case "bigint": + case "string": + case "boolean": + case "symbol": + case "undefined": + return l === i; + case "number": + return l === i || Object.is(l, i); + case "function": + return l === i; + case "object": + return Bi(l, i, p, d); + } + return Bi(l, i, p, d); +} +function Bi(l, i, s, c) { + if (Object.is(l, i)) + return !0; + let o = Es(l), p = Es(i); + if (o === Sf && (o = hs), p === Sf && (p = hs), o !== p) + return !1; + switch (o) { + case qp: + return l.toString() === i.toString(); + case jp: { + const g = l.valueOf(), S = i.valueOf(); + return kp(g, S); + } + case Hp: + case _p: + case Bp: + return Object.is(l.valueOf(), i.valueOf()); + case Np: + return l.source === i.source && l.flags === i.flags; + case Fv: + return l === i; + } + s = s ?? /* @__PURE__ */ new Map(); + const d = s.get(l), m = s.get(i); + if (d != null && m != null) + return d === i; + s.set(l, i), s.set(i, l); + try { + switch (o) { + case Cp: { + if (l.size !== i.size) + return !1; + for (const [g, S] of l.entries()) + if (!i.has(g) || !Ni(S, i.get(g), g, l, i, s, c)) + return !1; + return !0; + } + case Lp: { + if (l.size !== i.size) + return !1; + const g = Array.from(l.values()), S = Array.from(i.values()); + for (let A = 0; A < g.length; A++) { + const w = g[A], x = S.findIndex((_) => Ni(w, _, void 0, l, i, s, c)); + if (x === -1) + return !1; + S.splice(x, 1); + } + return !0; + } + case Xp: + case Gp: + case Zp: + case Vp: + case Kp: + case Wv: + case Pp: + case Jp: + case Fp: + case kv: + case $p: + case Wp: { + if (bf(l) !== bf(i) || l.length !== i.length) + return !1; + for (let g = 0; g < l.length; g++) + if (!Ni(l[g], i[g], g, l, i, s, c)) + return !1; + return !0; + } + case Yp: + return l.byteLength !== i.byteLength ? !1 : Bi(new Uint8Array(l), new Uint8Array(i), s, c); + case Qp: + return l.byteLength !== i.byteLength || l.byteOffset !== i.byteOffset ? !1 : Bi(new Uint8Array(l), new Uint8Array(i), s, c); + case $v: + return l.name === i.name && l.message === i.message; + case hs: { + if (!(Bi(l.constructor, i.constructor, s, c) || Pm(l) && Pm(i))) + return !1; + const S = [...Object.keys(l), ...vf(l)], A = [...Object.keys(i), ...vf(i)]; + if (S.length !== A.length) + return !1; + for (let w = 0; w < S.length; w++) { + const x = S[w], _ = l[x]; + if (!Object.hasOwn(i, x)) + return !1; + const B = i[x]; + if (!Ni(_, B, x, l, i, s, c)) + return !1; + } + return !0; + } + default: + return !1; + } + } finally { + s.delete(l), s.delete(i); + } +} +function _i(l, i) { + return eS(l, i, Kv); +} +function Ip(l) { + switch (typeof l) { + case "number": + case "symbol": + return !1; + case "string": + return l.includes(".") || l.includes("[") || l.includes("]"); + } +} +function Nf(l) { + return typeof l == "string" || typeof l == "symbol" ? l : Object.is(l?.valueOf?.(), -0) ? "-0" : String(l); +} +function ey(l) { + if (l == null) + return ""; + if (typeof l == "string") + return l; + if (Array.isArray(l)) + return l.map(ey).join(","); + const i = String(l); + return i === "0" && Object.is(Number(l), -0) ? "-0" : i; +} +function qf(l) { + if (Array.isArray(l)) + return l.map(Nf); + if (typeof l == "symbol") + return [l]; + l = ey(l); + const i = [], s = l.length; + if (s === 0) + return i; + let c = 0, o = "", p = "", d = !1; + for (l.charCodeAt(0) === 46 && (i.push(""), c++); c < s; ) { + const m = l[c]; + p ? m === "\\" && c + 1 < s ? (c++, o += l[c]) : m === p ? p = "" : o += m : d ? m === '"' || m === "'" ? p = m : m === "]" ? (d = !1, i.push(o), o = "") : o += m : m === "[" ? (d = !0, o && (i.push(o), o = "")) : m === "." ? o && (i.push(o), o = "") : o += m, c++; + } + return o && i.push(o), i; +} +function mt(l, i, s) { + if (l == null) + return s; + switch (typeof i) { + case "string": { + if (ws(i)) + return s; + const c = l[i]; + return c === void 0 ? Ip(i) ? mt(l, qf(i), s) : s : c; + } + case "number": + case "symbol": { + typeof i == "number" && (i = Nf(i)); + const c = l[i]; + return c === void 0 ? s : c; + } + default: { + if (Array.isArray(i)) + return tS(l, i, s); + if (Object.is(i?.valueOf(), -0) ? i = "-0" : i = String(i), ws(i)) + return s; + const c = l[i]; + return c === void 0 ? s : c; + } + } +} +function tS(l, i, s) { + if (i.length === 0) + return s; + let c = l; + for (let o = 0; o < i.length; o++) { + if (c == null || ws(i[o])) + return s; + c = c[i[o]]; + } + return c === void 0 ? s : c; +} +function Jm(l) { + return l !== null && (typeof l == "object" || typeof l == "function"); +} +const nS = /^(?:0|[1-9]\d*)$/; +function ty(l, i = Number.MAX_SAFE_INTEGER) { + switch (typeof l) { + case "number": + return Number.isInteger(l) && l >= 0 && l < i; + case "symbol": + return !1; + case "string": + return nS.test(l); + } +} +function lS(l) { + return l !== null && typeof l == "object" && Es(l) === "[object Arguments]"; +} +function aS(l, i) { + let s; + if (Array.isArray(i) ? s = i : typeof i == "string" && Ip(i) && l?.[i] == null ? s = qf(i) : s = [i], s.length === 0) + return !1; + let c = l; + for (let o = 0; o < s.length; o++) { + const p = s[o]; + if ((c == null || !Object.hasOwn(c, p)) && !((Array.isArray(c) || lS(c)) && ty(p) && p < c.length)) + return !1; + c = c[p]; + } + return !0; +} +const iS = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, rS = /^\w*$/; +function sS(l, i) { + return Array.isArray(l) ? !1 : typeof l == "number" || typeof l == "boolean" || l == null || Vv(l) ? !0 : typeof l == "string" && (rS.test(l) || !iS.test(l)) || i != null && Object.hasOwn(i, l); +} +const uS = (l, i, s) => { + const c = l[i]; + (!(Object.hasOwn(l, i) && kp(c, s)) || s === void 0 && !(i in l)) && (l[i] = s); +}; +function cS(l, i, s, c) { + if (l == null && !Jm(l)) + return l; + let o; + sS(i, l) ? o = [i] : Array.isArray(i) ? o = i : o = qf(i); + const p = s(mt(l, o)); + let d = l; + for (let m = 0; m < o.length && d != null; m++) { + const g = Nf(o[m]); + if (ws(g)) + continue; + let S; + if (m === o.length - 1) + S = p; + else { + const A = d[g], w = c?.(A, g, l); + S = w !== void 0 ? w : Jm(A) ? A : ty(o[m + 1]) ? [] : {}; + } + uS(d, g, S), d = d[g]; + } + return l; +} +function _n(l, i, s) { + return cS(l, i, () => s, () => { + }); +} +function ny() { +} +function jf(l) { + return l == null || typeof l != "object" && typeof l != "function"; +} +function Hf(l) { + return ArrayBuffer.isView(l) && !(l instanceof DataView); +} +function fS(l) { + if (jf(l)) + return l; + if (Array.isArray(l) || Hf(l) || l instanceof ArrayBuffer || typeof SharedArrayBuffer < "u" && l instanceof SharedArrayBuffer) + return l.slice(0); + const i = Object.getPrototypeOf(l), s = i.constructor; + if (l instanceof Date || l instanceof Map || l instanceof Set) + return new s(l); + if (l instanceof RegExp) { + const c = new s(l); + return c.lastIndex = l.lastIndex, c; + } + if (l instanceof DataView) + return new s(l.buffer.slice(0)); + if (l instanceof Error) { + const c = new s(l.message); + return c.stack = l.stack, c.name = l.name, c.cause = l.cause, c; + } + if (typeof File < "u" && l instanceof File) + return new s([l], l.name, { type: l.type, lastModified: l.lastModified }); + if (typeof l == "object") { + const c = Object.create(i); + return Object.assign(c, l); + } + return l; +} +function Fm(l) { + if (!l || typeof l != "object") + return !1; + const i = Object.getPrototypeOf(l); + return i === null || i === Object.prototype || Object.getPrototypeOf(i) === null ? Object.prototype.toString.call(l) === "[object Object]" : !1; +} +function $m(l) { + return typeof l == "object" && l !== null; +} +function As(l) { + return Object.getOwnPropertySymbols(l).filter((i) => Object.prototype.propertyIsEnumerable.call(l, i)); +} +function Os(l) { + return l == null ? l === void 0 ? "[object Undefined]" : "[object Null]" : Object.prototype.toString.call(l); +} +const ly = "[object RegExp]", Bf = "[object String]", _f = "[object Number]", Cf = "[object Boolean]", Rs = "[object Arguments]", ay = "[object Symbol]", iy = "[object Date]", ry = "[object Map]", sy = "[object Set]", uy = "[object Array]", oS = "[object Function]", cy = "[object ArrayBuffer]", ms = "[object Object]", dS = "[object Error]", fy = "[object DataView]", oy = "[object Uint8Array]", dy = "[object Uint8ClampedArray]", hy = "[object Uint16Array]", my = "[object Uint32Array]", hS = "[object BigUint64Array]", py = "[object Int8Array]", yy = "[object Int16Array]", gy = "[object Int32Array]", mS = "[object BigInt64Array]", vy = "[object Float32Array]", Sy = "[object Float64Array]"; +function pS(l, i) { + return Da(l, void 0, l, /* @__PURE__ */ new Map(), i); +} +function Da(l, i, s, c = /* @__PURE__ */ new Map(), o = void 0) { + const p = o?.(l, i, s, c); + if (p != null) + return p; + if (jf(l)) + return l; + if (c.has(l)) + return c.get(l); + if (Array.isArray(l)) { + const d = new Array(l.length); + c.set(l, d); + for (let m = 0; m < l.length; m++) + d[m] = Da(l[m], m, s, c, o); + return Object.hasOwn(l, "index") && (d.index = l.index), Object.hasOwn(l, "input") && (d.input = l.input), d; + } + if (l instanceof Date) + return new Date(l.getTime()); + if (l instanceof RegExp) { + const d = new RegExp(l.source, l.flags); + return d.lastIndex = l.lastIndex, d; + } + if (l instanceof Map) { + const d = /* @__PURE__ */ new Map(); + c.set(l, d); + for (const [m, g] of l) + d.set(m, Da(g, m, s, c, o)); + return d; + } + if (l instanceof Set) { + const d = /* @__PURE__ */ new Set(); + c.set(l, d); + for (const m of l) + d.add(Da(m, void 0, s, c, o)); + return d; + } + if (typeof Buffer < "u" && Buffer.isBuffer(l)) + return l.subarray(); + if (Hf(l)) { + const d = new (Object.getPrototypeOf(l)).constructor(l.length); + c.set(l, d); + for (let m = 0; m < l.length; m++) + d[m] = Da(l[m], m, s, c, o); + return d; + } + if (l instanceof ArrayBuffer || typeof SharedArrayBuffer < "u" && l instanceof SharedArrayBuffer) + return l.slice(0); + if (l instanceof DataView) { + const d = new DataView(l.buffer.slice(0), l.byteOffset, l.byteLength); + return c.set(l, d), ql(d, l, s, c, o), d; + } + if (typeof File < "u" && l instanceof File) { + const d = new File([l], l.name, { + type: l.type + }); + return c.set(l, d), ql(d, l, s, c, o), d; + } + if (l instanceof Blob) { + const d = new Blob([l], { type: l.type }); + return c.set(l, d), ql(d, l, s, c, o), d; + } + if (l instanceof Error) { + const d = new l.constructor(); + return c.set(l, d), d.message = l.message, d.name = l.name, d.stack = l.stack, d.cause = l.cause, ql(d, l, s, c, o), d; + } + if (typeof l == "object" && yS(l)) { + const d = Object.create(Object.getPrototypeOf(l)); + return c.set(l, d), ql(d, l, s, c, o), d; + } + return l; +} +function ql(l, i, s = l, c, o) { + const p = [...Object.keys(i), ...As(i)]; + for (let d = 0; d < p.length; d++) { + const m = p[d], g = Object.getOwnPropertyDescriptor(l, m); + (g == null || g.writable) && (l[m] = Da(i[m], m, s, c, o)); + } +} +function yS(l) { + switch (Os(l)) { + case Rs: + case uy: + case cy: + case fy: + case Cf: + case iy: + case vy: + case Sy: + case py: + case yy: + case gy: + case ry: + case _f: + case ms: + case ly: + case sy: + case Bf: + case ay: + case oy: + case dy: + case hy: + case my: + return !0; + default: + return !1; + } +} +function gS(l, i) { + return l === i || Number.isNaN(l) && Number.isNaN(i); +} +function vS(l, i, s) { + return qi(l, i, void 0, void 0, void 0, void 0, s); +} +function qi(l, i, s, c, o, p, d) { + const m = d(l, i, s, c, o, p); + if (m !== void 0) + return m; + if (typeof l == typeof i) + switch (typeof l) { + case "bigint": + case "string": + case "boolean": + case "symbol": + case "undefined": + return l === i; + case "number": + return l === i || Object.is(l, i); + case "function": + return l === i; + case "object": + return Ci(l, i, p, d); + } + return Ci(l, i, p, d); +} +function Ci(l, i, s, c) { + if (Object.is(l, i)) + return !0; + let o = Os(l), p = Os(i); + if (o === Rs && (o = ms), p === Rs && (p = ms), o !== p) + return !1; + switch (o) { + case Bf: + return l.toString() === i.toString(); + case _f: { + const g = l.valueOf(), S = i.valueOf(); + return gS(g, S); + } + case Cf: + case iy: + case ay: + return Object.is(l.valueOf(), i.valueOf()); + case ly: + return l.source === i.source && l.flags === i.flags; + case oS: + return l === i; + } + s = s ?? /* @__PURE__ */ new Map(); + const d = s.get(l), m = s.get(i); + if (d != null && m != null) + return d === i; + s.set(l, i), s.set(i, l); + try { + switch (o) { + case ry: { + if (l.size !== i.size) + return !1; + for (const [g, S] of l.entries()) + if (!i.has(g) || !qi(S, i.get(g), g, l, i, s, c)) + return !1; + return !0; + } + case sy: { + if (l.size !== i.size) + return !1; + const g = Array.from(l.values()), S = Array.from(i.values()); + for (let A = 0; A < g.length; A++) { + const w = g[A], x = S.findIndex((_) => qi(w, _, void 0, l, i, s, c)); + if (x === -1) + return !1; + S.splice(x, 1); + } + return !0; + } + case uy: + case oy: + case dy: + case hy: + case my: + case hS: + case py: + case yy: + case gy: + case mS: + case vy: + case Sy: { + if (typeof Buffer < "u" && Buffer.isBuffer(l) !== Buffer.isBuffer(i) || l.length !== i.length) + return !1; + for (let g = 0; g < l.length; g++) + if (!qi(l[g], i[g], g, l, i, s, c)) + return !1; + return !0; + } + case cy: + return l.byteLength !== i.byteLength ? !1 : Ci(new Uint8Array(l), new Uint8Array(i), s, c); + case fy: + return l.byteLength !== i.byteLength || l.byteOffset !== i.byteOffset ? !1 : Ci(new Uint8Array(l), new Uint8Array(i), s, c); + case dS: + return l.name === i.name && l.message === i.message; + case ms: { + if (!(Ci(l.constructor, i.constructor, s, c) || Fm(l) && Fm(i))) + return !1; + const S = [...Object.keys(l), ...As(l)], A = [...Object.keys(i), ...As(i)]; + if (S.length !== A.length) + return !1; + for (let w = 0; w < S.length; w++) { + const x = S[w], _ = l[x]; + if (!Object.hasOwn(i, x)) + return !1; + const B = i[x]; + if (!qi(_, B, x, l, i, s, c)) + return !1; + } + return !0; + } + default: + return !1; + } + } finally { + s.delete(l), s.delete(i); + } +} +function SS(l, i) { + return vS(l, i, ny); +} +function bS(l) { + switch (typeof l) { + case "number": + case "symbol": + return !1; + case "string": + return l.includes(".") || l.includes("[") || l.includes("]"); + } +} +function ES(l) { + return Object.is(l, -0) ? "-0" : l.toString(); +} +function by(l) { + const i = [], s = l.length; + if (s === 0) + return i; + let c = 0, o = "", p = "", d = !1; + for (l.charCodeAt(0) === 46 && (i.push(""), c++); c < s; ) { + const m = l[c]; + p ? m === "\\" && c + 1 < s ? (c++, o += l[c]) : m === p ? p = "" : o += m : d ? m === '"' || m === "'" ? p = m : m === "]" ? (d = !1, i.push(o), o = "") : o += m : m === "[" ? (d = !0, o && (i.push(o), o = "")) : m === "." ? o && (i.push(o), o = "") : o += m, c++; + } + return o && i.push(o), i; +} +function zs(l, i, s) { + if (l == null) + return s; + switch (typeof i) { + case "string": { + const c = l[i]; + return c === void 0 ? bS(i) ? zs(l, by(i), s) : s : c; + } + case "number": + case "symbol": { + typeof i == "number" && (i = ES(i)); + const c = l[i]; + return c === void 0 ? s : c; + } + default: { + if (Array.isArray(i)) + return wS(l, i, s); + Object.is(i?.valueOf(), -0) ? i = "-0" : i = String(i); + const c = l[i]; + return c === void 0 ? s : c; + } + } +} +function wS(l, i, s) { + if (i.length === 0) + return s; + let c = l; + for (let o = 0; o < i.length; o++) { + if (c == null) + return s; + c = c[i[o]]; + } + return c === void 0 ? s : c; +} +function AS(l, i) { + return pS(l, (s, c, o, p) => { + if (typeof l == "object") + switch (Object.prototype.toString.call(l)) { + case _f: + case Bf: + case Cf: { + const d = new l.constructor(l?.valueOf()); + return ql(d, l), d; + } + case Rs: { + const d = {}; + return ql(d, l), d.length = l.length, d[Symbol.iterator] = l[Symbol.iterator], d; + } + default: + return; + } + }); +} +function Wm(l) { + return AS(l); +} +const OS = /^(?:0|[1-9]\d*)$/; +function RS(l, i = Number.MAX_SAFE_INTEGER) { + switch (typeof l) { + case "number": + return Number.isInteger(l) && l >= 0 && l < i; + case "symbol": + return !1; + case "string": + return OS.test(l); + } +} +function km(l) { + return l !== null && typeof l == "object" && Os(l) === "[object Arguments]"; +} +function zS(l, i, s) { + const c = Array.isArray(i) ? i : typeof i == "string" ? by(i) : [i]; + let o = l; + for (let d = 0; d < c.length - 1; d++) { + const m = c[d], g = c[d + 1]; + o[m] == null && (o[m] = RS(g) ? [] : {}), o = o[m]; + } + const p = c[c.length - 1]; + return o[p] = s, l; +} +function DS(l, i, { signal: s, edges: c } = {}) { + let o, p = null; + const d = c != null && c.includes("leading"), m = c == null || c.includes("trailing"), g = () => { + p !== null && (l.apply(o, p), o = void 0, p = null); + }, S = () => { + m && g(), _(); + }; + let A = null; + const w = () => { + A != null && clearTimeout(A), A = setTimeout(() => { + A = null, S(); + }, i); + }, x = () => { + A !== null && (clearTimeout(A), A = null); + }, _ = () => { + x(), o = void 0, p = null; + }, B = () => { + x(), g(); + }, Z = function(...oe) { + if (s?.aborted) + return; + o = this, p = oe; + const C = A == null; + w(), d && C && g(); + }; + return Z.schedule = w, Z.cancel = _, Z.flush = B, s?.addEventListener("abort", _, { once: !0 }), Z; +} +function MS(l, i = 0, s = {}) { + typeof s != "object" && (s = {}); + const { signal: c, leading: o = !1, trailing: p = !0, maxWait: d } = s, m = Array(2); + o && (m[0] = "leading"), p && (m[1] = "trailing"); + let g, S = null; + const A = DS(function(..._) { + g = l.apply(this, _), S = null; + }, i, { signal: c, edges: m }), w = function(..._) { + if (d != null) { + if (S === null) + S = Date.now(); + else if (Date.now() - S >= d) + return g = l.apply(this, _), S = Date.now(), A.cancel(), A.schedule(), g; + } + return A.apply(this, _), g; + }, x = () => (A.flush(), g); + return w.cancel = A.cancel, w.flush = x, w; +} +function TS(l) { + return Hf(l); +} +function US(l) { + if (typeof l != "object" || l == null) + return !1; + if (Object.getPrototypeOf(l) === null) + return !0; + if (Object.prototype.toString.call(l) !== "[object Object]") { + const s = l[Symbol.toStringTag]; + return s == null || !Object.getOwnPropertyDescriptor(l, Symbol.toStringTag)?.writable ? !1 : l.toString() === `[object ${s}]`; + } + let i = l; + for (; Object.getPrototypeOf(i) !== null; ) + i = Object.getPrototypeOf(i); + return Object.getPrototypeOf(l) === i; +} +function xS(l, ...i) { + const s = i.slice(0, -1), c = i[i.length - 1]; + let o = l; + for (let p = 0; p < s.length; p++) { + const d = s[p]; + o = ps(o, d, c, /* @__PURE__ */ new Map()); + } + return o; +} +function ps(l, i, s, c) { + if (jf(l) && (l = Object(l)), i == null || typeof i != "object") + return l; + if (c.has(i)) + return fS(c.get(i)); + if (c.set(i, l), Array.isArray(i)) { + i = i.slice(); + for (let p = 0; p < i.length; p++) + i[p] = i[p] ?? void 0; + } + const o = [...Object.keys(i), ...As(i)]; + for (let p = 0; p < o.length; p++) { + const d = o[p]; + let m = i[d], g = l[d]; + if (km(m) && (m = { ...m }), km(g) && (g = { ...g }), typeof Buffer < "u" && Buffer.isBuffer(m) && (m = Wm(m)), Array.isArray(m)) + if (typeof g == "object" && g != null) { + const A = [], w = Reflect.ownKeys(g); + for (let x = 0; x < w.length; x++) { + const _ = w[x]; + A[_] = g[_]; + } + g = A; + } else + g = []; + const S = s(g, m, d, l, i, c); + S != null ? l[d] = S : Array.isArray(m) || $m(g) && $m(m) ? l[d] = ps(g, m, s, c) : g == null && US(m) ? l[d] = ps({}, m, s, c) : g == null && TS(m) ? l[d] = Wm(m) : (g === void 0 || m !== void 0) && (l[d] = m); + } + return l; +} +function Ds(l, ...i) { + return xS(l, ...i, ny); +} +const Lf = (l) => typeof File < "u" && l instanceof File || l instanceof Blob || typeof FileList < "u" && l instanceof FileList && l.length > 0, Xs = (l) => l instanceof FormData ? !0 : Lf(l) || typeof l == "object" && l !== null && Object.values(l).some((i) => Xs(i)); +let Ms = class extends Error { + response; + constructor(i) { + super(`HTTP error ${i.status}`), this.name = "HttpResponseError", this.response = i; + } +}, Ey = class extends Error { + constructor(i = "Request was cancelled") { + super(i), this.name = "HttpCancelledError"; + } +}, NS = class extends Error { + constructor(i = "Network error") { + super(i), this.name = "HttpNetworkError"; + } +}; +function qS(l) { + const i = new URLSearchParams(); + return Object.entries(l).forEach(([s, c]) => { + c != null && (Array.isArray(c) ? c.forEach((o) => i.append(`${s}[]`, String(o))) : typeof c == "object" ? i.append(s, JSON.stringify(c)) : i.append(s, String(c))); + }), i.toString(); +} +function jS(l, i, s) { + if (i && !l.startsWith("http://") && !l.startsWith("https://") && (l = i.replace(/\/$/, "") + "/" + l.replace(/^\//, "")), s && Object.keys(s).length > 0) { + const c = qS(s); + c && (l += (l.includes("?") ? "&" : "?") + c); + } + return l; +} +function HS() { + return typeof window > "u" ? null : window.axios?.defaults?.headers?.common?.["X-Requested-With"] ?? null; +} +function wy(l, i = new FormData(), s = null) { + for (const c in l) + Object.prototype.hasOwnProperty.call(l, c) && Ay(i, s ? `${s}[${c}]` : c, l[c]); + return i; +} +function Ay(l, i, s) { + if (Array.isArray(s)) + return s.forEach((c, o) => Ay(l, `${i}[${o}]`, c)); + if (s instanceof Date) + return l.append(i, s.toISOString()); + if (typeof File < "u" && s instanceof File) + return l.append(i, s, s.name); + if (s instanceof Blob) + return l.append(i, s); + if (typeof s == "boolean") + return l.append(i, s ? "1" : "0"); + if (typeof s == "string") + return l.append(i, s); + if (typeof s == "number") + return l.append(i, `${s}`); + if (s == null) + return l.append(i, ""); + wy(s, l, i); +} +function BS(l, i) { + if (l != null) + return l instanceof FormData ? l : typeof l == "object" && Xs(l) ? wy(l) : typeof l == "object" || i["Content-Type"]?.includes("application/json") ? JSON.stringify(l) : String(l); +} +function _S(l) { + const i = {}; + return l.forEach((s, c) => { + i[c.toLowerCase()] = s; + }), i; +} +function CS(l = {}) { + let i = l.xsrfCookieName ?? "XSRF-TOKEN", s = l.xsrfHeaderName ?? "X-XSRF-TOKEN"; + function c() { + if (typeof document > "u") + return null; + const o = document.cookie.match(new RegExp("(^|;\\s*)" + i + "=([^;]*)")); + return o ? decodeURIComponent(o[2]) : null; + } + return { + setXsrfCookieName(o) { + i = o; + }, + setXsrfHeaderName(o) { + s = o; + }, + async request(o) { + const p = jS(o.url, o.baseURL, o.params), d = o.method.toUpperCase(), m = {}, g = HS(); + g && (m["X-Requested-With"] = g), o.data !== void 0 && !["GET", "DELETE"].includes(d) && !(o.data instanceof FormData) && !Xs(o.data) && (m["Content-Type"] = "application/json"), o.headers && Object.entries(o.headers).forEach(([B, Z]) => { + Z !== void 0 && (m[B] = String(Z)); + }); + const S = c(); + S && !["GET", "HEAD", "OPTIONS"].includes(d) && (m[s] = S); + let A = o.signal, w; + const x = o.timeout ?? 3e4; + if (x > 0 && !A) { + const B = new AbortController(); + A = B.signal, w = setTimeout(() => B.abort(), x); + } + const _ = ["GET", "DELETE"].includes(d) ? void 0 : BS(o.data, m); + _ instanceof FormData && delete m["Content-Type"]; + try { + const B = await fetch(p, { + method: d, + headers: m, + body: _, + signal: A, + credentials: o.credentials ?? "same-origin" + }); + w && clearTimeout(w); + let Z; + B.headers.get("content-type")?.includes("application/json") ? Z = await B.json() : Z = await B.text(); + const C = { + status: B.status, + data: Z, + headers: _S(B.headers) + }; + if (!B.ok) + throw new Ms(C); + return C; + } catch (B) { + throw w && clearTimeout(w), B instanceof Ms ? B : B instanceof DOMException && B.name === "AbortError" ? new Ey() : B instanceof TypeError ? new NS(B.message) : B; + } + } + }; +} +const Ef = CS(); +let Xf = Ef, Yf, Oy, Ry = "same-origin", zy = (l) => `${l.method}:${l.baseURL ?? Yf ?? ""}${l.url}`, Dy = (l) => l.status === 204 && l.headers["precognition-success"] === "true"; +const Ts = {}, gt = { + get: (l, i = {}, s = {}) => Ri(Oi("get", l, i, s)), + post: (l, i = {}, s = {}) => Ri(Oi("post", l, i, s)), + patch: (l, i = {}, s = {}) => Ri(Oi("patch", l, i, s)), + put: (l, i = {}, s = {}) => Ri(Oi("put", l, i, s)), + delete: (l, i = {}, s = {}) => Ri(Oi("delete", l, i, s)), + useHttpClient(l) { + return Xf = l, gt; + }, + withBaseURL(l) { + return Yf = l, gt; + }, + withTimeout(l) { + return Oy = l, gt; + }, + withCredentials(l) { + return Ry = typeof l == "string" ? l : l ? "include" : "omit", gt; + }, + fingerprintRequestsUsing(l) { + return zy = l === null ? () => null : l, gt; + }, + determineSuccessUsing(l) { + return Dy = l, gt; + }, + withXsrfCookieName(l) { + return Ef.setXsrfCookieName(l), gt; + }, + withXsrfHeaderName(l) { + return Ef.setXsrfHeaderName(l), gt; + } +}, Oi = (l, i, s, c) => ({ + url: i, + method: l, + ...c, + ...["get", "delete"].includes(l) ? { + params: Ds({}, s, c?.params) + } : { + data: Ds({}, s, c?.data) + } +}), Ri = (l = {}) => { + const i = [ + LS, + YS, + QS + ].reduce((s, c) => c(s), l); + return (i.onBefore ?? (() => !0))() === !1 ? Promise.resolve(null) : ((i.onStart ?? (() => null))(), Xf.request({ + method: i.method, + url: i.url, + baseURL: i.baseURL ?? Yf, + data: i.data, + params: i.params, + headers: i.headers, + signal: i.signal, + timeout: i.timeout, + credentials: Ry + }).then(async (s) => { + i.precognitive && Im(s); + const c = s.status; + let o = s; + return i.precognitive && i.onPrecognitionSuccess && Dy(s) && (o = await Promise.resolve(i.onPrecognitionSuccess(s) ?? o)), i.onSuccess && XS(c) && (o = await Promise.resolve(i.onSuccess(o) ?? o)), (ep(i, c) ?? ((d) => d))(o) ?? o; + }, (s) => { + if (GS(s)) + return Promise.reject(s); + const c = s; + return i.precognitive && Im(c.response), (ep(i, c.response.status) ?? ((p, d) => Promise.reject(d)))(c.response, c); + }).finally(i.onFinish ?? (() => null))); +}, LS = (l) => { + const i = l.only ?? l.validate; + return { + ...l, + timeout: l.timeout ?? Oy, + precognitive: l.precognitive !== !1, + fingerprint: typeof l.fingerprint > "u" ? zy(l, Xf) : l.fingerprint, + headers: { + ...l.headers, + Accept: "application/json", + "Content-Type": ZS(l), + ...l.precognitive !== !1 ? { + Precognition: !0 + } : {}, + ...i ? { + "Precognition-Validate-Only": Array.from(i).join() + } : {} + } + }; +}, XS = (l) => l >= 200 && l < 300, YS = (l) => (typeof l.fingerprint != "string" || (Ts[l.fingerprint]?.abort(), delete Ts[l.fingerprint]), l), QS = (l) => typeof l.fingerprint != "string" || l.signal || !l.precognitive ? l : (Ts[l.fingerprint] = new AbortController(), { + ...l, + signal: Ts[l.fingerprint].signal +}), Im = (l) => { + if (l.headers?.precognition !== "true") + throw Error("Did not receive a Precognition response. Ensure you have the Precognition middleware in place for the route."); +}, GS = (l) => !(l instanceof Ms) || typeof l.response?.status != "number", ep = (l, i) => ({ + 401: l.onUnauthorized, + 403: l.onForbidden, + 404: l.onNotFound, + 409: l.onConflict, + 422: l.onValidationError, + 423: l.onLocked +})[i], ZS = (l) => l.headers?.["Content-Type"] ?? l.headers?.["Content-type"] ?? l.headers?.["content-type"] ?? (Xs(l.data) ? "multipart/form-data" : "application/json"), VS = (l, i) => { + if (!l.includes("*")) + return [l]; + const s = l.split("."); + let c = [""]; + for (const o of s) + if (o === "*") { + const p = []; + for (const d of c) { + const m = d ? zs(i, d) : i; + if (Array.isArray(m)) + for (let g = 0; g < m.length; g++) + p.push(d ? `${d}.${g}` : String(g)); + else if (m !== null && typeof m == "object") + for (const g of Object.keys(m)) + p.push(d ? `${d}.${g}` : g); + } + c = p; + } else + c = c.map((p) => p ? `${p}.${o}` : o); + return c; +}, KS = (l, i) => i.includes("*") ? new RegExp("^" + i.replace(/\./g, "\\.").replace(/\*/g, "[^.]+") + "$").test(l) : l === i, tp = (l, i) => Object.fromEntries(Object.entries(l).filter(([s]) => !i.some((c) => KS(s, c)))), PS = (l, i = {}) => { + const s = { + errorsChanged: [], + touchedChanged: [], + validatingChanged: [], + validatedChanged: [] + }; + let c = !1, o = !1; + const p = (j) => j !== o ? (o = j, s.validatingChanged) : []; + let d = []; + const m = (j) => { + const H = [...new Set(j)]; + return d.length !== H.length || !H.every((Y) => d.includes(Y)) ? (d = H, s.validatedChanged) : []; + }, g = () => d.filter((j) => typeof w[j] > "u"); + let S = []; + const A = (j) => { + const H = [...new Set(j)]; + return S.length !== H.length || !H.every((Y) => S.includes(Y)) ? (S = H, s.touchedChanged) : []; + }; + let w = {}; + const x = (j) => { + const H = FS(j); + return SS(w, H) ? [] : (w = H, s.errorsChanged); + }, _ = (j) => { + const H = { ...w }; + return delete H[Li(j)], x(H); + }, B = () => Object.keys(w).length > 0; + let Z = 1500; + const oe = (j) => { + Z = j, K.cancel(), K = Be(); + }; + let C = i, ce = null, me = [], fe = null; + const Be = () => MS((j) => { + l({ + get: (H, Y = {}, $ = {}) => gt.get(H, ue(Y), I($, j, Y)), + post: (H, Y = {}, $ = {}) => gt.post(H, ue(Y), I($, j, Y)), + patch: (H, Y = {}, $ = {}) => gt.patch(H, ue(Y), I($, j, Y)), + put: (H, Y = {}, $ = {}) => gt.put(H, ue(Y), I($, j, Y)), + delete: (H, Y = {}, $ = {}) => gt.delete(H, ue(Y), I($, j, Y)) + }).catch((H) => H instanceof Ey || H instanceof Ms && H.response?.status === 422 ? null : Promise.reject(H)); + }, Z, { leading: !0, trailing: !0 }); + let K = Be(); + const I = (j, H, Y = {}) => { + const $ = { + ...j, + ...H + }, be = Array.from($.only ?? $.validate ?? S); + return { + ...H, + ...Ds({}, j, H), + only: be, + timeout: $.timeout ?? 5e3, + onValidationError: (b, N) => ([ + ...m([...d, ...be]), + ...x(Ds(tp({ ...w }, be), b.data.errors)) + ].forEach((L) => L()), $.onValidationError ? $.onValidationError(b, N) : Promise.reject(N)), + onSuccess: (b) => (m([...d, ...be]).forEach((N) => N()), $.onSuccess ? $.onSuccess(b) : b), + onPrecognitionSuccess: (b) => ([ + ...m([...d, ...be]), + ...x(tp({ ...w }, be)) + ].forEach((N) => N()), $.onPrecognitionSuccess ? $.onPrecognitionSuccess(b) : b), + onBefore: () => { + const b = S.some((P) => P.includes("*")), N = b ? [...new Set(S.flatMap((P) => VS(P, Y)))] : S; + return $.onBeforeValidation && $.onBeforeValidation({ data: Y, touched: N }, { data: C, touched: me }) === !1 || ($.onBefore || (() => !0))() === !1 ? !1 : (b && A(N).forEach((P) => P()), fe = S, ce = Y, !0); + }, + onStart: () => { + p(!0).forEach((b) => b()), ($.onStart ?? (() => null))(); + }, + onFinish: () => { + p(!1).forEach((b) => b()), me = fe, C = ce, fe = ce = null, ($.onFinish ?? (() => null))(); + } + }; + }, ne = (j, H, Y) => { + if (typeof j > "u") { + const $ = Array.from(Y?.only ?? Y?.validate ?? []); + A([...S, ...$]).forEach((be) => be()), K(Y ?? {}); + return; + } + if (Lf(H) && !c) { + console.warn('Precognition file validation is not active. Call the "validateFiles" function on your form to enable it.'); + return; + } + j = Li(j), (j.includes("*") || zs(C, j) !== H) && (A([j, ...S]).forEach(($) => $()), K(Y ?? {})); + }, ue = (j) => c === !1 ? wf(j) : j, V = { + touched: () => S, + validate(j, H, Y) { + return typeof j == "object" && !("target" in j) && (Y = j, j = H = void 0), ne(j, H, Y), V; + }, + touch(j) { + const H = Array.isArray(j) ? j : [Li(j)]; + return A([...S, ...H]).forEach((Y) => Y()), V; + }, + validating: () => o, + valid: g, + errors: () => w, + hasErrors: B, + setErrors(j) { + return x(j).forEach((H) => H()), V; + }, + forgetError(j) { + return _(j).forEach((H) => H()), V; + }, + defaults(j) { + return i = j, C = j, V; + }, + reset(...j) { + if (j.length === 0) + A([]).forEach((H) => H()); + else { + const H = [...S]; + j.forEach((Y) => { + H.includes(Y) && H.splice(H.indexOf(Y), 1), zS(C, Y, zs(i, Y)); + }), A(H).forEach((Y) => Y()); + } + return V; + }, + setTimeout(j) { + return oe(j), V; + }, + on(j, H) { + return s[j].push(H), V; + }, + validateFiles() { + return c = !0, V; + }, + withoutFileValidation() { + return c = !1, V; + } + }; + return V; +}, JS = (l) => Object.keys(l).reduce((i, s) => ({ + ...i, + [s]: Array.isArray(l[s]) ? l[s][0] : l[s] +}), {}), FS = (l) => Object.keys(l).reduce((i, s) => ({ + ...i, + [s]: typeof l[s] == "string" ? [l[s]] : l[s] +}), {}), Li = (l) => typeof l != "string" ? l.target.name : l, wf = (l) => { + const i = { ...l }; + return Object.keys(i).forEach((s) => { + const c = i[s]; + if (c !== null) { + if (Lf(c)) { + delete i[s]; + return; + } + if (Array.isArray(c)) { + i[s] = Object.values(wf({ ...c })); + return; + } + if (typeof c == "object") { + i[s] = wf(i[s]); + return; + } + } + }), i; +}; +var $S = class { + config = {}; + defaults; + constructor(l) { + this.defaults = l; + } + extend(l) { + return l && (this.defaults = { ...this.defaults, ...l }), this; + } + replace(l) { + this.config = l; + } + get(l) { + return aS(this.config, l) ? mt(this.config, l) : mt(this.defaults, l); + } + set(l, i) { + typeof l == "string" ? _n(this.config, l, i) : Object.entries(l).forEach(([s, c]) => { + _n(this.config, s, c); + }); + } +}, Gi = new $S({ + form: { + recentlySuccessfulDuration: 2e3, + forceIndicesArrayFormatInFormData: !0, + withAllErrors: !1 + }, + prefetch: { + cacheFor: 3e4, + hoverDelay: 75 + } +}); +function Us(l, i) { + let s; + return function(...c) { + clearTimeout(s), s = setTimeout(() => l.apply(this, c), i); + }; +} +function Qt(l, i) { + return document.dispatchEvent(new CustomEvent(`inertia:${l}`, i)); +} +var np = (l) => Qt("before", { cancelable: !0, detail: { visit: l } }), WS = (l) => Qt("error", { detail: { errors: l } }), kS = (l) => Qt("networkError", { cancelable: !0, detail: { error: l } }), IS = (l) => Qt("finish", { detail: { visit: l } }), lp = (l) => Qt("httpException", { cancelable: !0, detail: { response: l } }), eb = (l) => Qt("beforeUpdate", { detail: { page: l } }), xs = (l) => Qt("navigate", { detail: { page: l } }), tb = (l) => Qt("progress", { detail: { progress: l } }), nb = (l) => Qt("start", { detail: { visit: l } }), lb = (l) => Qt("success", { detail: { page: l } }), ab = (l, i) => Qt("prefetched", { detail: { fetchedAt: Date.now(), response: l, visit: i } }), ib = (l) => Qt("prefetching", { detail: { visit: l } }), Ns = (l) => Qt("flash", { detail: { flash: l } }), Mt = class { + static locationVisitKey = "inertiaLocationVisit"; + static set(l, i) { + typeof window < "u" && window.sessionStorage.setItem(l, JSON.stringify(i)); + } + static get(l) { + if (typeof window < "u") + return JSON.parse(window.sessionStorage.getItem(l) || "null"); + } + static merge(l, i) { + const s = this.get(l); + s === null ? this.set(l, i) : this.set(l, { ...s, ...i }); + } + static remove(l) { + typeof window < "u" && window.sessionStorage.removeItem(l); + } + static removeNested(l, i) { + const s = this.get(l); + s !== null && (delete s[i], this.set(l, s)); + } + static exists(l) { + try { + return this.get(l) !== null; + } catch { + return !1; + } + } + static clear() { + typeof window < "u" && window.sessionStorage.clear(); + } +}, rb = async (l) => { + if (typeof window > "u") + throw new Error("Unable to encrypt history"); + const i = My(), s = await Ty(), c = await db(s); + if (!c) + throw new Error("Unable to encrypt history"); + return await ub(i, c, l); +}, Ta = { + key: "historyKey", + iv: "historyIv" +}, sb = async (l) => { + const i = My(), s = await Ty(); + if (!s) + throw new Error("Unable to decrypt history"); + return await cb(i, s, l); +}, ub = async (l, i, s) => { + if (typeof window > "u") + throw new Error("Unable to encrypt history"); + if (typeof window.crypto.subtle > "u") + return console.warn("Encryption is not supported in this environment. SSL is required."), Promise.resolve(s); + const c = new TextEncoder(), o = JSON.stringify(s), p = new Uint8Array(o.length * 3), d = c.encodeInto(o, p); + return window.crypto.subtle.encrypt( + { + name: "AES-GCM", + iv: l + }, + i, + p.subarray(0, d.written) + ); +}, cb = async (l, i, s) => { + if (typeof window.crypto.subtle > "u") + return console.warn("Decryption is not supported in this environment. SSL is required."), Promise.resolve(s); + const c = await window.crypto.subtle.decrypt( + { + name: "AES-GCM", + iv: l + }, + i, + s + ); + return JSON.parse(new TextDecoder().decode(c)); +}, My = () => { + const l = Mt.get(Ta.iv); + if (l) + return new Uint8Array(l); + const i = window.crypto.getRandomValues(new Uint8Array(12)); + return Mt.set(Ta.iv, Array.from(i)), i; +}, fb = async () => typeof window.crypto.subtle > "u" ? (console.warn("Encryption is not supported in this environment. SSL is required."), Promise.resolve(null)) : window.crypto.subtle.generateKey( + { + name: "AES-GCM", + length: 256 + }, + !0, + ["encrypt", "decrypt"] +), ob = async (l) => { + if (typeof window.crypto.subtle > "u") + return console.warn("Encryption is not supported in this environment. SSL is required."), Promise.resolve(); + const i = await window.crypto.subtle.exportKey("raw", l); + Mt.set(Ta.key, Array.from(new Uint8Array(i))); +}, db = async (l) => { + if (l) + return l; + const i = await fb(); + return i ? (await ob(i), i) : null; +}, Ty = async () => { + const l = Mt.get(Ta.key); + return l ? await window.crypto.subtle.importKey( + "raw", + new Uint8Array(l), + { + name: "AES-GCM", + length: 256 + }, + !0, + ["encrypt", "decrypt"] + ) : null; +}, Uy = (l, i, s) => { + if (l === i) + return !0; + for (const c in l) + if (!s.includes(c) && l[c] !== i[c] && !hb(l[c], i[c])) + return !1; + for (const c in i) + if (!s.includes(c) && !(c in l)) + return !1; + return !0; +}, hb = (l, i) => { + switch (typeof l) { + case "object": + return Uy(l, i, []); + case "function": + return l.toString() === i.toString(); + default: + return l === i; + } +}, mb = { + ms: 1, + s: 1e3, + m: 1e3 * 60, + h: 1e3 * 60 * 60, + d: 1e3 * 60 * 60 * 24 +}, ap = (l) => { + if (typeof l == "number") + return l; + for (const [i, s] of Object.entries(mb)) + if (l.endsWith(i)) + return parseFloat(l) * s; + return parseInt(l); +}, pb = class { + cached = []; + inFlightRequests = []; + removalTimers = []; + currentUseId = null; + add(l, i, { cacheFor: s, cacheTags: c }) { + if (this.findInFlight(l)) + return Promise.resolve(); + const p = this.findCached(l); + if (!l.fresh && p && p.staleTimestamp > Date.now()) + return Promise.resolve(); + const [d, m] = this.extractStaleValues(s), g = new Promise((S, A) => { + i({ + ...l, + onCancel: () => { + this.remove(l), l.onCancel(), A(); + }, + onError: (w) => { + this.remove(l), l.onError(w), A(); + }, + onPrefetching(w) { + l.onPrefetching(w); + }, + onPrefetched(w, x) { + l.onPrefetched(w, x); + }, + onPrefetchResponse(w) { + S(w); + }, + onPrefetchError(w) { + pn.removeFromInFlight(l), A(w); + } + }); + }).then((S) => { + this.remove(l); + const A = S.getPageResponse(); + q.mergeOncePropsIntoResponse(A), this.cached.push({ + params: { ...l }, + staleTimestamp: Date.now() + d, + expiresAt: Date.now() + m, + response: g, + singleUse: m === 0, + timestamp: Date.now(), + inFlight: !1, + tags: Array.isArray(c) ? c : [c] + }); + const w = this.getShortestOncePropTtl(A); + return this.scheduleForRemoval( + l, + w ? Math.min(m, w) : m + ), this.removeFromInFlight(l), S.handlePrefetch(), S; + }); + return this.inFlightRequests.push({ + params: { ...l }, + response: g, + staleTimestamp: null, + inFlight: !0 + }), g; + } + removeAll() { + this.cached = [], this.removalTimers.forEach((l) => { + clearTimeout(l.timer); + }), this.removalTimers = []; + } + removeByTags(l) { + this.cached = this.cached.filter((i) => !i.tags.some((s) => l.includes(s))); + } + remove(l) { + this.cached = this.cached.filter((i) => !this.paramsAreEqual(i.params, l)), this.clearTimer(l); + } + removeFromInFlight(l) { + this.inFlightRequests = this.inFlightRequests.filter((i) => !this.paramsAreEqual(i.params, l)); + } + extractStaleValues(l) { + const [i, s] = this.cacheForToStaleAndExpires(l); + return [ap(i), ap(s)]; + } + cacheForToStaleAndExpires(l) { + if (!Array.isArray(l)) + return [l, l]; + switch (l.length) { + case 0: + return [0, 0]; + case 1: + return [l[0], l[0]]; + default: + return [l[0], l[1]]; + } + } + clearTimer(l) { + const i = this.removalTimers.find((s) => this.paramsAreEqual(s.params, l)); + i && (clearTimeout(i.timer), this.removalTimers = this.removalTimers.filter((s) => s !== i)); + } + scheduleForRemoval(l, i) { + if (!(typeof window > "u") && (this.clearTimer(l), i > 0)) { + const s = window.setTimeout(() => this.remove(l), i); + this.removalTimers.push({ + params: l, + timer: s + }); + } + } + get(l) { + return this.findCached(l) || this.findInFlight(l); + } + use(l, i) { + const s = `${i.url.pathname}-${Date.now()}-${Math.random().toString(36).substring(7)}`; + return this.currentUseId = s, l.response.then((c) => { + if (this.currentUseId === s) + return c.mergeParams({ ...i, onPrefetched: () => { + } }), this.removeSingleUseItems(i), c.handle(); + }); + } + removeSingleUseItems(l) { + this.cached = this.cached.filter((i) => this.paramsAreEqual(i.params, l) ? !i.singleUse : !0); + } + findCached(l) { + return this.cached.find((i) => this.paramsAreEqual(i.params, l)) || null; + } + findInFlight(l) { + return this.inFlightRequests.find((i) => this.paramsAreEqual(i.params, l)) || null; + } + withoutPurposePrefetchHeader(l) { + const i = sn(l); + return i.headers.Purpose === "prefetch" && delete i.headers.Purpose, i; + } + paramsAreEqual(l, i) { + return Uy( + this.withoutPurposePrefetchHeader(l), + this.withoutPurposePrefetchHeader(i), + [ + "showProgress", + "replace", + "prefetch", + "preserveScroll", + "preserveState", + "onBefore", + "onBeforeUpdate", + "onStart", + "onProgress", + "onFinish", + "onCancel", + "onSuccess", + "onError", + "onFlash", + "onPrefetched", + "onCancelToken", + "onPrefetching", + "async", + "viewTransition", + "optimistic", + "component", + "pageProps" + ] + ); + } + updateCachedOncePropsFromCurrentPage() { + this.cached.forEach((l) => { + l.response.then((i) => { + const s = i.getPageResponse(); + q.mergeOncePropsIntoResponse(s, { force: !0 }); + for (const [d, m] of Object.entries(s.deferredProps ?? {})) { + const g = m.filter((S) => mt(s.props, S) === void 0); + g.length > 0 ? s.deferredProps[d] = g : delete s.deferredProps[d]; + } + const c = this.getShortestOncePropTtl(s); + if (c === null) + return; + const o = l.expiresAt - Date.now(), p = Math.min(o, c); + p > 0 ? this.scheduleForRemoval(l.params, p) : this.remove(l.params); + }); + }); + } + getShortestOncePropTtl(l) { + const i = Object.values(l.onceProps ?? {}).map((s) => s.expiresAt).filter((s) => !!s); + return i.length === 0 ? null : Math.min(...i) - Date.now(); + } +}, pn = new pb(), lf = (l) => { + if (l.offsetParent === null) + return !1; + const i = l.getBoundingClientRect(), s = i.top < window.innerHeight && i.bottom >= 0, c = i.left < window.innerWidth && i.right >= 0; + return s && c; +}, yb = (l) => { + const i = (d) => { + const m = window.getComputedStyle(d); + return m.overflowY === "scroll" ? !0 : m.overflowY !== "auto" ? !1 : ["visible", "clip"].includes(m.overflowX) ? !0 : c(m.maxHeight, d.style.height) || o(d, "height"); + }, s = (d) => { + const m = window.getComputedStyle(d); + return m.overflowX === "scroll" ? !0 : m.overflowX !== "auto" ? !1 : ["visible", "clip"].includes(m.overflowY) ? !0 : c(m.maxWidth, d.style.width) || o(d, "width"); + }, c = (d, m) => !!(d && d !== "none" && d !== "0px" || m && m !== "auto" && m !== "0"), o = (d, m) => { + const g = d.parentElement; + if (!g) + return !1; + const S = window.getComputedStyle(g); + if (["flex", "inline-flex"].includes(S.display)) { + const A = ["column", "column-reverse"].includes(S.flexDirection); + return m === "height" ? A : !A; + } + return ["grid", "inline-grid"].includes(S.display); + }; + let p = l?.parentElement; + for (; p; ) { + const d = i(p) || s(p); + if (window.getComputedStyle(p).display !== "contents" && d) + return p; + p = p.parentElement; + } + return null; +}, xy = (l, i) => { + if (!i) + return l.filter((p) => lf(p)); + const s = l.indexOf(i), c = [], o = []; + for (let p = s; p >= 0; p--) { + const d = l[p]; + if (lf(d)) + c.push(d); + else + break; + } + for (let p = s + 1; p < l.length; p++) { + const d = l[p]; + if (lf(d)) + o.push(d); + else + break; + } + return [...c.reverse(), ...o]; +}, Xi = (l, i = 1) => { + window.requestAnimationFrame(() => { + i > 1 ? Xi(l, i - 1) : l(); + }); +}, ji = typeof window > "u", gb = !ji && /Firefox/i.test(window.navigator.userAgent), Tt = class { + static save() { + Se.saveScrollPositions(this.getScrollRegions()); + } + static getScrollRegions() { + return Array.from(this.regions()).map((l) => ({ + top: l.scrollTop, + left: l.scrollLeft + })); + } + static regions() { + return document.querySelectorAll("[scroll-region]"); + } + static scrollToTop() { + if (gb && getComputedStyle(document.documentElement).scrollBehavior === "smooth") + return Xi(() => window.scrollTo(0, 0), 2); + window.scrollTo(0, 0); + } + static reset() { + !ji && window.location.hash || this.scrollToTop(), this.regions().forEach((i) => { + typeof i.scrollTo == "function" ? i.scrollTo(0, 0) : (i.scrollTop = 0, i.scrollLeft = 0); + }), this.save(), this.scrollToAnchor(); + } + static scrollToAnchor() { + const l = ji ? null : window.location.hash; + l && setTimeout(() => { + const i = document.getElementById(l.slice(1)); + i ? i.scrollIntoView() : this.scrollToTop(); + }); + } + static restore(l) { + ji || window.requestAnimationFrame(() => { + this.restoreDocument(), this.restoreScrollRegions(l); + }); + } + static restoreScrollRegions(l) { + ji || this.regions().forEach((i, s) => { + const c = l[s]; + c && (typeof i.scrollTo == "function" ? i.scrollTo(c.left, c.top) : (i.scrollTop = c.top, i.scrollLeft = c.left)); + }); + } + static restoreDocument() { + const l = Se.getDocumentScrollPosition(); + window.scrollTo(l.left, l.top); + } + static onScroll(l) { + const i = l.target; + typeof i.hasAttribute == "function" && i.hasAttribute("scroll-region") && this.save(); + } + static onWindowScroll() { + Se.saveDocumentScrollPosition({ + top: window.scrollY, + left: window.scrollX + }); + } +}, Qf = (l) => typeof File < "u" && l instanceof File || l instanceof Blob || typeof FileList < "u" && l instanceof FileList && l.length > 0; +function Af(l) { + return Qf(l) || l instanceof FormData && Array.from(l.values()).some((i) => Af(i)) || typeof l == "object" && l !== null && Object.values(l).some((i) => Af(i)); +} +var Of = (l) => l instanceof FormData; +function Ny(l, i = new FormData(), s = null, c = "brackets") { + l = l || {}; + for (const o in l) + Object.prototype.hasOwnProperty.call(l, o) && jy(i, qy(s, o, "indices"), l[o], c); + return i; +} +function qy(l, i, s) { + return l ? s === "brackets" ? `${l}[]` : `${l}[${i}]` : i; +} +function jy(l, i, s, c) { + if (Array.isArray(s)) + return Array.from(s.keys()).forEach( + (o) => jy(l, qy(i, o.toString(), c), s[o], c) + ); + if (s instanceof Date) + return l.append(i, s.toISOString()); + if (s instanceof File) + return l.append(i, s, s.name); + if (s instanceof Blob) + return l.append(i, s); + if (typeof s == "boolean") + return l.append(i, s ? "1" : "0"); + if (typeof s == "string") + return l.append(i, s); + if (typeof s == "number") + return l.append(i, `${s}`); + if (s == null) + return l.append(i, ""); + Ny(s, l, i, c); +} +function vb(l) { + return /\[\d+\]/.test(decodeURIComponent(l.search)); +} +function Sb(l) { + if (!l || l === "?") + return {}; + const i = {}; + return l.replace(/^\?/, "").split("&").filter(Boolean).forEach((s) => { + const [c, o] = Eb(s); + wb(i, ip(c), ip(o)); + }), i; +} +function bb(l, i) { + const s = []; + return Rf(l, "", s, i), s.length ? "?" + s.join("&") : ""; +} +function Eb(l) { + const i = l.indexOf("="); + return i === -1 ? [l, ""] : [l.substring(0, i), l.substring(i + 1)]; +} +function ip(l) { + return decodeURIComponent(l.replace(/\+/g, " ")); +} +function wb(l, i, s) { + const c = Ab(i); + let o = l; + for (; c.length > 1; ) { + const d = c.shift(), m = c[0] === ""; + (typeof o[d] != "object" || o[d] === null) && (o[d] = m ? [] : {}), o = o[d]; + } + const p = c.shift(); + p === "" && Array.isArray(o) ? o.push(s) : o[p] = s; +} +function Ab(l) { + const i = [], s = l.split("[")[0]; + s && i.push(s); + let c; + const o = /\[([^\]]*)\]/g; + for (; (c = o.exec(l)) !== null; ) + i.push(c[1]); + return i; +} +function Rf(l, i, s, c) { + if (l !== void 0) { + if (l === null) { + s.push(`${i}=`); + return; + } + if (Array.isArray(l)) { + l.forEach((o, p) => { + const d = c === "indices" ? `${i}[${p}]` : `${i}[]`; + Rf(o, d, s, c); + }); + return; + } + if (typeof l == "object") { + Object.keys(l).forEach((o) => { + Rf(l[o], i ? `${i}[${o}]` : o, s, c); + }); + return; + } + s.push(`${i}=${encodeURIComponent(String(l))}`); + } +} +function tn(l) { + return new URL(l.toString(), typeof window > "u" ? void 0 : window.location.toString()); +} +var Ob = (l, i, s, c, o) => { + let p = typeof l == "string" ? tn(l) : l; + if ((Af(i) || c) && !Of(i) && (Gi.get("form.forceIndicesArrayFormatInFormData") && (o = "indices"), i = Ny(i, new FormData(), null, o)), Of(i)) + return [p, i]; + const [d, m] = Ys(s, p, i, o); + return [tn(d), m]; +}; +function Ys(l, i, s, c = "brackets") { + const o = l === "get" && !Of(s) && Object.keys(s).length > 0, p = By(i.toString()), d = p || i.toString().startsWith("/") || i.toString() === "", m = !d && !i.toString().startsWith("#") && !i.toString().startsWith("?"), g = /^[.]{1,2}([/]|$)/.test(i.toString()), S = i.toString().includes("?") || o, A = i.toString().includes("#"), w = new URL(i.toString(), typeof window > "u" ? "http://localhost" : window.location.toString()); + if (o) { + const x = vb(w) ? "indices" : c; + w.search = bb({ ...Sb(w.search), ...s }, x); + } + return [ + [ + p ? `${w.protocol}//${w.host}` : "", + d ? w.pathname : "", + m ? w.pathname.substring(g ? 0 : 1) : "", + S ? w.search : "", + A ? w.hash : "" + ].join(""), + o ? {} : s + ]; +} +function qs(l) { + return l = new URL(l.href), l.hash = "", l; +} +var rp = (l, i) => { + l.hash && !i.hash && qs(l).href === i.href && (i.hash = l.hash); +}, js = (l, i) => qs(l).href === qs(i).href, Rb = (l, i) => l.origin === i.origin && l.pathname === i.pathname; +function un(l) { + return l !== null && typeof l == "object" && l !== void 0 && "url" in l && "method" in l; +} +function Hy(l) { + return l.component ? typeof l.component != "string" ? (console.error( + `The "component" property on the URL method pair received multiple components (${Object.keys(l.component).join(", ")}), but only a single component string is supported for instant visits. Use the withComponent() method to specify which component to use.` + ), null) : l.component : null; +} +function By(l) { + return /^([a-z][a-z0-9+.-]*:)?\/\/[^/]/i.test(l); +} +function zb(l, i) { + const s = typeof l == "string" ? tn(l) : l; + return i ? `${s.protocol}//${s.host}${s.pathname}${s.search}${s.hash}` : `${s.pathname}${s.search}${s.hash}`; +} +var Db = class { + page; + swapComponent; + resolveComponent; + onFlashCallback; + componentId = {}; + listeners = []; + isFirstPageLoad = !0; + cleared = !1; + pendingDeferredProps = null; + historyQuotaExceeded = !1; + optimisticBaseline = {}; + pendingOptimistics = []; + optimisticCounter = 0; + init({ + initialPage: l, + swapComponent: i, + resolveComponent: s, + onFlash: c + }) { + return this.page = { ...l, flash: l.flash ?? {} }, this.swapComponent = i, this.resolveComponent = s, this.onFlashCallback = c, yn.on("historyQuotaExceeded", () => { + this.historyQuotaExceeded = !0; + }), this; + } + set(l, { + replace: i = !1, + preserveScroll: s = !1, + preserveState: c = !1, + viewTransition: o = !1 + } = {}) { + Object.keys(l.deferredProps || {}).length && (this.pendingDeferredProps = { + deferredProps: l.deferredProps, + component: l.component, + url: l.url + }, l.initialDeferredProps === void 0 && (l.initialDeferredProps = l.deferredProps)), this.componentId = {}; + const p = this.componentId; + return l.clearHistory && Se.clear(), this.resolve(l.component, l).then((d) => { + if (p !== this.componentId) + return; + l.rememberedState ??= {}; + const m = typeof window > "u", g = m ? new URL(l.url) : window.location, S = !m && s ? Tt.getScrollRegions() : []; + i = i || js(tn(l.url), g); + const A = { ...l, flash: {} }; + return new Promise( + (w) => i ? Se.replaceState(A, w) : Se.pushState(A, w) + ).then(() => { + const w = !this.isTheSame(l); + if (!w && Object.keys(l.props.errors || {}).length > 0 && (o = !1), this.page = l, this.cleared = !1, this.hasOnceProps() && pn.updateCachedOncePropsFromCurrentPage(), w && this.fireEventsFor("newComponent"), this.isFirstPageLoad && this.fireEventsFor("firstLoad"), this.isFirstPageLoad = !1, this.historyQuotaExceeded) { + this.historyQuotaExceeded = !1; + return; + } + return this.swap({ + component: d, + page: l, + preserveState: c, + viewTransition: o + }).then(() => { + s ? window.requestAnimationFrame(() => Tt.restoreScrollRegions(S)) : Tt.reset(), this.pendingDeferredProps && this.pendingDeferredProps.component === l.component && this.pendingDeferredProps.url === l.url && yn.fireInternalEvent("loadDeferredProps", this.pendingDeferredProps.deferredProps), this.pendingDeferredProps = null, i || xs(l); + }); + }); + }); + } + setQuietly(l, { + preserveState: i = !1 + } = {}) { + return this.resolve(l.component, l).then((s) => (this.page = l, this.cleared = !1, Se.setCurrent(l), this.swap({ component: s, page: l, preserveState: i, viewTransition: !1 }))); + } + clear() { + this.cleared = !0; + } + isCleared() { + return this.cleared; + } + get() { + return this.page; + } + getWithoutFlashData() { + return { ...this.page, flash: {} }; + } + hasOnceProps() { + return Object.keys(this.page.onceProps ?? {}).length > 0; + } + merge(l) { + this.page = { ...this.page, ...l }; + } + setPropsQuietly(l) { + return this.page = { ...this.page, props: l }, this.resolve(this.page.component, this.page).then((i) => this.swap({ component: i, page: this.page, preserveState: !0, viewTransition: !1 })); + } + setFlash(l) { + this.page = { ...this.page, flash: l }, this.onFlashCallback?.(l); + } + setUrlHash(l) { + this.page.url.includes(l) || (this.page.url += l); + } + remember(l) { + this.page.rememberedState = l; + } + swap({ + component: l, + page: i, + preserveState: s, + viewTransition: c + }) { + const o = () => this.swapComponent({ component: l, page: i, preserveState: s }); + if (!c || !document?.startViewTransition || document.visibilityState === "hidden") + return o(); + const p = typeof c == "boolean" ? () => null : c; + return new Promise((d) => { + const m = document.startViewTransition(() => o().then(d)); + p(m); + }); + } + resolve(l, i) { + return Promise.resolve(this.resolveComponent(l, i)); + } + nextOptimisticId() { + return ++this.optimisticCounter; + } + setBaseline(l, i) { + l in this.optimisticBaseline || (this.optimisticBaseline[l] = i); + } + updateBaseline(l, i) { + l in this.optimisticBaseline && (this.optimisticBaseline[l] = i); + } + hasBaseline(l) { + return l in this.optimisticBaseline; + } + registerOptimistic(l, i) { + this.pendingOptimistics.push({ id: l, callback: i }); + } + unregisterOptimistic(l) { + this.pendingOptimistics = this.pendingOptimistics.filter((i) => i.id !== l); + } + replayOptimistics() { + const l = Object.keys(this.optimisticBaseline); + if (l.length === 0) + return {}; + const i = sn(this.page.props); + for (const c of l) + i[c] = sn(this.optimisticBaseline[c]); + for (const { callback: c } of this.pendingOptimistics) { + const o = c(sn(i)); + o && Object.assign(i, o); + } + const s = {}; + for (const c of l) + s[c] = i[c]; + return s; + } + pendingOptimisticCount() { + return this.pendingOptimistics.length; + } + clearOptimisticState() { + this.optimisticBaseline = {}, this.pendingOptimistics = []; + } + isTheSame(l) { + return this.page.component === l.component; + } + on(l, i) { + return this.listeners.push({ event: l, callback: i }), () => { + this.listeners = this.listeners.filter((s) => s.event !== l && s.callback !== i); + }; + } + fireEventsFor(l) { + this.listeners.filter((i) => i.event === l).forEach((i) => i.callback()); + } + mergeOncePropsIntoResponse(l, { force: i = !1 } = {}) { + Object.entries(l.onceProps ?? {}).forEach(([s, c]) => { + const o = this.page.onceProps?.[s]; + o !== void 0 && (i || mt(l.props, c.prop) === void 0) && (_n(l.props, c.prop, mt(this.page.props, o.prop)), l.onceProps[s].expiresAt = o.expiresAt); + }); + } +}, q = new Db(), Qs = class { + items = []; + processingPromise = null; + add(l) { + return this.items.push(l), this.process(); + } + process() { + return this.processingPromise ??= this.processNext().finally(() => { + this.processingPromise = null; + }), this.processingPromise; + } + processNext() { + const l = this.items.shift(); + return l ? Promise.resolve(l()).then(() => this.processNext()) : Promise.resolve(); + } +}, Aa = typeof window > "u", zi = new Qs(), sp = !Aa && /CriOS/.test(window.navigator.userAgent), Mb = class { + rememberedState = "rememberedState"; + scrollRegions = "scrollRegions"; + preserveUrl = !1; + current = {}; + // We need initialState for `restore` + initialState = null; + remember(l, i) { + this.replaceState({ + ...q.getWithoutFlashData(), + rememberedState: { + ...q.get()?.rememberedState ?? {}, + [i]: l + } + }); + } + restore(l) { + if (!Aa) + return this.current[this.rememberedState]?.[l] !== void 0 ? this.current[this.rememberedState]?.[l] : this.initialState?.[this.rememberedState]?.[l]; + } + pushState(l, i = null) { + if (!Aa) { + if (this.preserveUrl) { + i && i(); + return; + } + this.current = l, zi.add(() => this.getPageData(l).then((s) => { + const c = () => this.doPushState({ page: s }, l.url).then(() => i?.()); + return sp ? new Promise((o) => { + setTimeout(() => c().then(o)); + }) : c(); + })); + } + } + clonePageProps(l) { + try { + return structuredClone(l.props), l; + } catch { + return { + ...l, + props: sn(l.props) + }; + } + } + getPageData(l) { + const i = this.clonePageProps(l); + return new Promise((s) => l.encryptHistory ? rb(i).then(s) : s(i)); + } + processQueue() { + return zi.process(); + } + decrypt(l = null) { + if (Aa) + return Promise.resolve(l ?? q.get()); + const i = l ?? window.history.state?.page; + return this.decryptPageData(i).then((s) => { + if (!s) + throw new Error("Unable to decrypt history"); + return this.initialState === null ? this.initialState = s ?? void 0 : this.current = s ?? {}, s; + }); + } + decryptPageData(l) { + return l instanceof ArrayBuffer ? sb(l) : Promise.resolve(l); + } + saveScrollPositions(l) { + zi.add(() => Promise.resolve().then(() => { + if (window.history.state?.page && !_i(this.getScrollRegions(), l)) + return this.doReplaceState({ + page: window.history.state.page, + scrollRegions: l + }); + })); + } + saveDocumentScrollPosition(l) { + zi.add(() => Promise.resolve().then(() => { + if (window.history.state?.page && !_i(this.getDocumentScrollPosition(), l)) + return this.doReplaceState({ + page: window.history.state.page, + documentScrollPosition: l + }); + })); + } + getScrollRegions() { + return window.history.state?.scrollRegions || []; + } + getDocumentScrollPosition() { + return window.history.state?.documentScrollPosition || { top: 0, left: 0 }; + } + replaceState(l, i = null) { + if (_i(this.current, l)) { + i && i(); + return; + } + const { flash: s, ...c } = l; + if (q.merge(c), !Aa) { + if (this.preserveUrl) { + i && i(); + return; + } + this.current = l, zi.add(() => this.getPageData(l).then((o) => { + const p = () => this.doReplaceState({ page: o }, l.url).then(() => i?.()); + return sp ? new Promise((d) => { + setTimeout(() => p().then(d)); + }) : p(); + })); + } + } + isHistoryThrottleError(l) { + return l instanceof Error && l.name === "SecurityError" && (l.message.includes("history.pushState") || l.message.includes("history.replaceState")); + } + isQuotaExceededError(l) { + return l instanceof Error && l.name === "QuotaExceededError"; + } + withThrottleProtection(l) { + return Promise.resolve().then(() => { + try { + return l(); + } catch (i) { + if (!this.isHistoryThrottleError(i)) + throw i; + console.error(i.message); + } + }); + } + doReplaceState(l, i) { + return this.withThrottleProtection(() => { + window.history.replaceState( + { + ...l, + scrollRegions: l.scrollRegions ?? window.history.state?.scrollRegions, + documentScrollPosition: l.documentScrollPosition ?? window.history.state?.documentScrollPosition + }, + "", + i + ); + }); + } + doPushState(l, i) { + return this.withThrottleProtection(() => { + try { + window.history.pushState(l, "", i); + } catch (s) { + if (!this.isQuotaExceededError(s)) + throw s; + yn.fireInternalEvent("historyQuotaExceeded", i); + } + }); + } + getState(l, i) { + return this.current?.[l] ?? i; + } + deleteState(l) { + this.current[l] !== void 0 && (delete this.current[l], this.replaceState(this.current)); + } + clearInitialState(l) { + this.initialState && this.initialState[l] !== void 0 && delete this.initialState[l]; + } + browserHasHistoryEntry() { + return !Aa && !!window.history.state?.page; + } + clear() { + Mt.remove(Ta.key), Mt.remove(Ta.iv); + } + setCurrent(l) { + this.current = l; + } + isValidState(l) { + return !!l.page; + } + getAllState() { + return this.current; + } +}; +typeof window < "u" && window.history.scrollRestoration && (window.history.scrollRestoration = "manual"); +var Se = new Mb(), Tb = class { + internalListeners = []; + init() { + typeof window < "u" && (window.addEventListener("popstate", this.handlePopstateEvent.bind(this)), window.addEventListener("pageshow", this.handlePageshowEvent.bind(this)), window.addEventListener("scroll", Us(Tt.onWindowScroll.bind(Tt), 100), !0)), typeof document < "u" && document.addEventListener("scroll", Us(Tt.onScroll.bind(Tt), 100), !0); + } + onGlobalEvent(l, i) { + const s = ((c) => { + const o = i(c); + c.cancelable && !c.defaultPrevented && o === !1 && c.preventDefault(); + }); + return this.registerListener(`inertia:${l}`, s); + } + on(l, i) { + return this.internalListeners.push({ event: l, listener: i }), () => { + this.internalListeners = this.internalListeners.filter((s) => s.listener !== i); + }; + } + onMissingHistoryItem() { + q.clear(), this.fireInternalEvent("missingHistoryItem"); + } + fireInternalEvent(l, ...i) { + this.internalListeners.filter((s) => s.event === l).forEach((s) => s.listener(...i)); + } + registerListener(l, i) { + return document.addEventListener(l, i), () => document.removeEventListener(l, i); + } + // bfcache restores pages without firing `popstate`, so we use `pageshow` to + // re-validate encrypted history entries after `clearHistory` removed the keys. + // https://web.dev/articles/bfcache + handlePageshowEvent(l) { + l.persisted && Se.decrypt().catch(() => this.onMissingHistoryItem()); + } + handlePopstateEvent(l) { + const i = l.state || null; + if (i === null) { + const s = tn(q.get().url); + s.hash = window.location.hash, Se.replaceState({ ...q.getWithoutFlashData(), url: s.href }), Tt.reset(); + return; + } + if (!Se.isValidState(i)) + return this.onMissingHistoryItem(); + Se.decrypt(i.page).then((s) => { + if (q.get().version !== s.version) { + this.onMissingHistoryItem(); + return; + } + Ze.cancelAll({ prefetch: !1 }), q.setQuietly(s, { preserveState: !1 }).then(() => { + Tt.restore(Se.getScrollRegions()), xs(q.get()); + const c = {}, o = q.get().props; + for (const [p, d] of Object.entries(s.initialDeferredProps ?? s.deferredProps ?? {})) { + const m = d.filter((g) => mt(o, g) === void 0); + m.length > 0 && (c[p] = m); + } + Object.keys(c).length > 0 && this.fireInternalEvent("loadDeferredProps", c); + }); + }).catch(() => { + this.onMissingHistoryItem(); + }); + } +}, yn = new Tb(), Ub = class { + type; + constructor() { + this.type = this.resolveType(); + } + resolveType() { + return typeof window > "u" ? "navigate" : window.performance?.getEntriesByType("navigation")[0]?.type ?? "navigate"; + } + get() { + return this.type; + } + isBackForward() { + return this.type === "back_forward"; + } + isReload() { + return this.type === "reload"; + } +}, af = new Ub(), xb = class { + static handle() { + this.clearRememberedStateOnReload(), [this.handleBackForward, this.handleLocation, this.handleDefault].find((i) => i.bind(this)()); + } + static clearRememberedStateOnReload() { + af.isReload() && (Se.deleteState(Se.rememberedState), Se.clearInitialState(Se.rememberedState)); + } + static handleBackForward() { + if (!af.isBackForward() || !Se.browserHasHistoryEntry()) + return !1; + const l = Se.getScrollRegions(); + return Se.decrypt().then((i) => { + q.set(i, { preserveScroll: !0, preserveState: !0 }).then(() => { + Tt.restore(l), xs(q.get()); + }); + }).catch(() => { + yn.onMissingHistoryItem(); + }), !0; + } + /** + * @link https://inertiajs.com/redirects#external-redirects + */ + static handleLocation() { + if (!Mt.exists(Mt.locationVisitKey)) + return !1; + const l = Mt.get(Mt.locationVisitKey) || {}; + return Mt.remove(Mt.locationVisitKey), typeof window < "u" && q.setUrlHash(window.location.hash), Se.decrypt(q.get()).then(() => { + const i = Se.getState(Se.rememberedState, {}), s = Se.getScrollRegions(); + q.remember(i), q.set(q.get(), { + preserveScroll: l.preserveScroll, + preserveState: !0 + }).then(() => { + l.preserveScroll && Tt.restore(s), this.fireInitialEvents(); + }); + }).catch(() => { + yn.onMissingHistoryItem(); + }), !0; + } + static handleDefault() { + typeof window < "u" && q.setUrlHash(window.location.hash), q.set(q.get(), { preserveScroll: !0, preserveState: !0 }).then(() => { + af.isReload() ? Tt.restore(Se.getScrollRegions()) : Tt.scrollToAnchor(), this.fireInitialEvents(); + }); + } + static fireInitialEvents() { + const l = q.get(); + xs(l), Object.keys(l.flash).length > 0 && queueMicrotask(() => Ns(l.flash)); + } +}, Nb = class { + id = null; + throttle = !1; + keepAlive = !1; + cb; + interval; + cbCount = 0; + constructor(l, i, s) { + this.keepAlive = s.keepAlive ?? !1, this.cb = i, this.interval = l, (s.autoStart ?? !0) && this.start(); + } + stop() { + this.id && clearInterval(this.id); + } + start() { + typeof window > "u" || (this.stop(), this.id = window.setInterval(() => { + (!this.throttle || this.cbCount % 10 === 0) && this.cb(), this.throttle && this.cbCount++; + }, this.interval)); + } + isInBackground(l) { + this.throttle = this.keepAlive ? !1 : l, this.throttle && (this.cbCount = 0); + } +}, qb = class { + polls = []; + constructor() { + this.setupVisibilityListener(); + } + add(l, i, s) { + const c = new Nb(l, i, s); + return this.polls.push(c), { + stop: () => c.stop(), + start: () => c.start() + }; + } + clear() { + this.polls.forEach((l) => l.stop()), this.polls = []; + } + setupVisibilityListener() { + typeof document > "u" || document.addEventListener( + "visibilitychange", + () => { + this.polls.forEach((l) => l.isInBackground(document.hidden)); + }, + !1 + ); + } +}, jb = new qb(), Hb = class { + requestHandlers = []; + responseHandlers = []; + errorHandlers = []; + onRequest(l) { + return this.requestHandlers.push(l), () => { + this.requestHandlers = this.requestHandlers.filter((i) => i !== l); + }; + } + onResponse(l) { + return this.responseHandlers.push(l), () => { + this.responseHandlers = this.responseHandlers.filter((i) => i !== l); + }; + } + onError(l) { + return this.errorHandlers.push(l), () => { + this.errorHandlers = this.errorHandlers.filter((i) => i !== l); + }; + } + async processRequest(l) { + let i = l; + for (const s of this.requestHandlers) + i = await s(i); + return i; + } + async processResponse(l) { + let i = l; + for (const s of this.responseHandlers) + i = await s(i); + return i; + } + async processError(l) { + for (const i of this.errorHandlers) + await i(l); + } +}, yt = new Hb(), Gf = class extends Error { + code; + url; + constructor(l, i, s) { + super(s ? `${l} (${s})` : l), this.name = "HttpError", this.code = i, this.url = s; + } +}, zf = class extends Gf { + response; + constructor(l, i, s) { + super(l, "ERR_HTTP_RESPONSE", s), this.name = "HttpResponseError", this.response = i; + } +}, Df = class extends Gf { + constructor(l = "Request was cancelled", i) { + super(l, "ERR_CANCELLED", i), this.name = "HttpCancelledError"; + } +}, up = class extends Gf { + cause; + constructor(l, i, s) { + super(l, "ERR_NETWORK", i), this.name = "HttpNetworkError", this.cause = s; + } +}; +function Bb(l) { + const i = document.cookie.match(new RegExp("(^|;\\s*)(" + l + ")=([^;]*)")); + return i ? decodeURIComponent(i[3]) : null; +} +function _b(l) { + const i = {}; + return l.getAllResponseHeaders().split(`\r +`).forEach((s) => { + const c = s.indexOf(":"); + c > 0 && (i[s.slice(0, c).toLowerCase().trim()] = s.slice(c + 1).trim()); + }), i; +} +function Cb(l, i) { + if (!i.headers) + return; + const s = i.data instanceof FormData; + Object.entries(i.headers).forEach(([c, o]) => { + (c.toLowerCase() !== "content-type" || !s) && l.setRequestHeader(c, String(o)); + }); +} +function Lb(l, i) { + if (!i || Object.keys(i).length === 0) + return l; + const [s] = Ys("get", l, i); + return s; +} +var _y = class { + xsrfCookieName; + xsrfHeaderName; + constructor(l = {}) { + this.xsrfCookieName = l.xsrfCookieName ?? "XSRF-TOKEN", this.xsrfHeaderName = l.xsrfHeaderName ?? "X-XSRF-TOKEN"; + } + async request(l) { + const i = await yt.processRequest(l); + try { + const s = await this.doRequest(i); + return await yt.processResponse(s); + } catch (s) { + throw (s instanceof zf || s instanceof up || s instanceof Df) && await yt.processError(s), s; + } + } + doRequest(l) { + return new Promise((i, s) => { + const c = new XMLHttpRequest(), o = Lb(l.url, l.params); + c.open(l.method.toUpperCase(), o, !0); + const p = Bb(this.xsrfCookieName); + p && c.setRequestHeader(this.xsrfHeaderName, p); + let d = null; + l.data !== null && l.data !== void 0 && (l.data instanceof FormData ? d = l.data : typeof l.data == "object" ? (d = JSON.stringify(l.data), !l.headers?.["Content-Type"] && !l.headers?.["content-type"] && c.setRequestHeader("Content-Type", "application/json")) : d = String(l.data)), Cb(c, l), l.onUploadProgress && (c.upload.onprogress = (m) => { + const g = m.lengthComputable ? m.loaded / m.total : void 0; + l.onUploadProgress({ + progress: g, + percentage: g ? Math.round(g * 100) : 0, + loaded: m.loaded, + total: m.lengthComputable ? m.total : void 0 + }); + }), l.signal && l.signal.addEventListener("abort", () => c.abort()), c.onabort = () => s(new Df("Request was cancelled", l.url)), c.onerror = () => s(new up("Network error", l.url)), c.onload = () => { + const m = { + status: c.status, + data: c.responseText, + headers: _b(c) + }; + c.status >= 400 ? s(new zf(`Request failed with status ${c.status}`, m, l.url)) : i(m); + }, c.send(d); + }); + } +}, Xb = new _y(), rf = Xb; +function Yb(l) { + return !("request" in l); +} +var Cy = { + /** + * Get the current HTTP client + */ + getClient() { + return rf; + }, + /** + * Set the HTTP client to use for all Inertia requests + */ + setClient(l) { + if (!Yb(l)) { + rf = l; + return; + } + rf = new _y(l), l.xsrfCookieName && gt.withXsrfCookieName(l.xsrfCookieName), l.xsrfHeaderName && gt.withXsrfHeaderName(l.xsrfHeaderName); + }, + /** + * Register a request handler that runs before each request + */ + onRequest: yt.onRequest.bind(yt), + /** + * Register a response handler that runs after each successful response + */ + onResponse: yt.onResponse.bind(yt), + /** + * Register an error handler that runs when a request fails + */ + onError: yt.onError.bind(yt), + /** + * Process a request config through all registered request handlers. + * For use by custom HttpClient implementations. + */ + processRequest: yt.processRequest.bind(yt), + /** + * Process a response through all registered response handlers. + * For use by custom HttpClient implementations. + */ + processResponse: yt.processResponse.bind(yt), + /** + * Process an error through all registered error handlers. + * For use by custom HttpClient implementations. + */ + processError: yt.processError.bind(yt) +}, ys = class gs { + callbacks = []; + params; + constructor(i) { + if (!i.prefetch) + this.params = i; + else { + const s = { + onBefore: this.wrapCallback(i, "onBefore"), + onBeforeUpdate: this.wrapCallback(i, "onBeforeUpdate"), + onStart: this.wrapCallback(i, "onStart"), + onProgress: this.wrapCallback(i, "onProgress"), + onFinish: this.wrapCallback(i, "onFinish"), + onCancel: this.wrapCallback(i, "onCancel"), + onSuccess: this.wrapCallback(i, "onSuccess"), + onError: this.wrapCallback(i, "onError"), + onHttpException: this.wrapCallback(i, "onHttpException"), + onNetworkError: this.wrapCallback(i, "onNetworkError"), + onFlash: this.wrapCallback(i, "onFlash"), + onCancelToken: this.wrapCallback(i, "onCancelToken"), + onPrefetched: this.wrapCallback(i, "onPrefetched"), + onPrefetching: this.wrapCallback(i, "onPrefetching") + }; + this.params = { + ...i, + ...s, + onPrefetchResponse: i.onPrefetchResponse || (() => { + }), + onPrefetchError: i.onPrefetchError || (() => { + }) + }; + } + } + static create(i) { + return new gs(i); + } + data() { + return this.params.method === "get" ? null : this.params.data; + } + queryParams() { + return this.params.method === "get" ? this.params.data : {}; + } + isPartial() { + return this.params.only.length > 0 || this.params.except.length > 0 || this.params.reset.length > 0; + } + isPrefetch() { + return this.params.prefetch === !0; + } + isDeferredPropsRequest() { + return this.params.deferredProps === !0; + } + onCancelToken(i) { + this.params.onCancelToken({ + cancel: i + }); + } + markAsFinished() { + this.params.completed = !0, this.params.cancelled = !1, this.params.interrupted = !1; + } + markAsCancelled({ cancelled: i = !0, interrupted: s = !1 }) { + this.params.onCancel(), this.params.completed = !1, this.params.cancelled = i, this.params.interrupted = s; + } + wasCancelledAtAll() { + return this.params.cancelled || this.params.interrupted; + } + onFinish() { + this.params.onFinish(this.params); + } + onStart() { + this.params.onStart(this.params); + } + onPrefetching() { + this.params.onPrefetching(this.params); + } + onPrefetchResponse(i) { + this.params.onPrefetchResponse && this.params.onPrefetchResponse(i); + } + onPrefetchError(i) { + this.params.onPrefetchError && this.params.onPrefetchError(i); + } + all() { + return this.params; + } + headers() { + const i = { + ...this.params.headers + }; + this.isPartial() && (i["X-Inertia-Partial-Component"] = q.get().component); + const s = this.params.only.concat(this.params.reset); + return s.length > 0 && (i["X-Inertia-Partial-Data"] = s.join(",")), this.params.except.length > 0 && (i["X-Inertia-Partial-Except"] = this.params.except.join(",")), this.params.reset.length > 0 && (i["X-Inertia-Reset"] = this.params.reset.join(",")), this.params.errorBag && this.params.errorBag.length > 0 && (i["X-Inertia-Error-Bag"] = this.params.errorBag), i; + } + setPreserveOptions(i) { + this.params.preserveScroll = gs.resolvePreserveOption(this.params.preserveScroll, i), this.params.preserveState = gs.resolvePreserveOption(this.params.preserveState, i); + } + runCallbacks() { + this.callbacks.forEach(({ name: i, args: s }) => { + this.params[i](...s); + }); + } + merge(i) { + this.params = { + ...this.params, + ...i + }; + } + wrapCallback(i, s) { + return (...c) => { + this.recordCallback(s, c), i[s](...c); + }; + } + recordCallback(i, s) { + this.callbacks.push({ name: i, args: s }); + } + static resolvePreserveOption(i, s) { + return typeof i == "function" ? i(s) : i === "errors" ? Object.keys(s.props.errors || {}).length > 0 : i; + } +}, Qb = { + createIframeAndPage(l) { + typeof l == "object" && (l = `All Inertia requests must receive a valid Inertia response, however a plain JSON response was received.
${JSON.stringify( + l + )}`); + const i = document.createElement("html"); + i.innerHTML = l, i.querySelectorAll("a").forEach((c) => c.setAttribute("target", "_top")); + const s = document.createElement("iframe"); + return s.style.backgroundColor = "white", s.style.borderRadius = "5px", s.style.width = "100%", s.style.height = "100%", { iframe: s, page: i }; + }, + show(l) { + const { iframe: i, page: s } = this.createIframeAndPage(l); + i.style.boxSizing = "border-box", i.style.display = "block"; + const c = document.createElement("dialog"); + c.id = "inertia-error-dialog", Object.assign(c.style, { + width: "calc(100vw - 100px)", + height: "calc(100vh - 100px)", + padding: "0", + margin: "auto", + border: "none", + backgroundColor: "transparent" + }); + const o = document.createElement("style"); + if (o.textContent = ` + dialog#inertia-error-dialog::backdrop { + background-color: rgba(0, 0, 0, 0.6); + } + + dialog#inertia-error-dialog:focus { + outline: none; + } + `, document.head.appendChild(o), c.addEventListener("click", (p) => { + p.target === c && c.close(); + }), c.addEventListener("close", () => { + o.remove(), c.remove(); + }), c.appendChild(i), document.body.prepend(c), c.showModal(), c.focus(), !i.contentWindow) + throw new Error("iframe not yet ready."); + i.contentWindow.document.open(), i.contentWindow.document.write(s.outerHTML), i.contentWindow.document.close(); + } +}, Gb = new Qs(), cp = class Ly { + constructor(i, s, c) { + this.requestParams = i, this.response = s, this.originatingPage = c; + } + wasPrefetched = !1; + processed = !1; + static create(i, s, c) { + return new Ly(i, s, c); + } + isProcessed() { + return this.processed; + } + async handlePrefetch() { + js(this.requestParams.all().url, window.location) && this.handle(); + } + async handle() { + return Gb.add(() => this.process()); + } + async process() { + if (this.requestParams.all().prefetch) + return this.wasPrefetched = !0, this.requestParams.all().prefetch = !1, this.requestParams.all().onPrefetched(this.response, this.requestParams.all()), ab(this.response, this.requestParams.all()), Promise.resolve(); + if (this.requestParams.runCallbacks(), this.processed = !0, !this.isInertiaResponse()) + return this.handleNonInertiaResponse(); + if (this.isHttpException()) { + const c = { + ...this.response, + data: this.getDataFromResponse(this.response.data) + }; + if (this.requestParams.all().onHttpException(c) === !1 || !lp(c)) + return; + } + await Se.processQueue(), Se.preserveUrl = this.requestParams.all().preserveUrl, await this.setPage(); + const { flash: i } = q.get(); + Object.keys(i).length > 0 && !this.requestParams.isDeferredPropsRequest() && (Ns(i), this.requestParams.all().onFlash(i)); + const s = q.get().props.errors || {}; + if (Object.keys(s).length > 0) { + const c = this.getScopedErrors(s); + return WS(c), this.requestParams.all().onError(c); + } + Ze.flushByCacheTags(this.requestParams.all().invalidateCacheTags || []), this.wasPrefetched || Ze.flush(q.get().url), lb(q.get()), await this.requestParams.all().onSuccess(q.get()), Se.preserveUrl = !1; + } + mergeParams(i) { + this.requestParams.merge(i); + } + getPageResponse() { + const i = this.getDataFromResponse(this.response.data); + return typeof i == "object" ? this.response.data = { ...i, flash: i.flash ?? {} } : this.response.data = i; + } + async handleNonInertiaResponse() { + if (this.isInertiaRedirect()) { + Ze.visit(this.getHeader("x-inertia-redirect"), { + ...this.requestParams.all(), + method: "get", + data: {} + }); + return; + } + if (this.isLocationVisit()) { + const s = tn(this.getHeader("x-inertia-location")); + return rp(this.requestParams.all().url, s), this.locationVisit(s); + } + const i = { + ...this.response, + data: this.getDataFromResponse(this.response.data) + }; + if (this.requestParams.all().onHttpException(i) !== !1 && lp(i)) + return Qb.show(i.data); + } + isInertiaResponse() { + return this.hasHeader("x-inertia"); + } + isHttpException() { + return this.response.status >= 400; + } + hasStatus(i) { + return this.response.status === i; + } + getHeader(i) { + return this.response.headers[i]; + } + hasHeader(i) { + return this.getHeader(i) !== void 0; + } + isInertiaRedirect() { + return this.hasStatus(409) && this.hasHeader("x-inertia-redirect"); + } + isLocationVisit() { + return this.hasStatus(409) && this.hasHeader("x-inertia-location"); + } + /** + * @link https://inertiajs.com/redirects#external-redirects + */ + locationVisit(i) { + try { + if (Mt.set(Mt.locationVisitKey, { + preserveScroll: this.requestParams.all().preserveScroll === !0 + }), typeof window > "u") + return; + js(window.location, i) ? window.location.reload() : window.location.href = i.href; + } catch { + return !1; + } + } + async setPage() { + const i = this.getPageResponse(); + return this.shouldSetPage(i) ? (this.mergeProps(i), q.mergeOncePropsIntoResponse(i), this.preserveOptimisticProps(i), this.preserveEqualProps(i), await this.setRememberedState(i), this.requestParams.setPreserveOptions(i), i.url = Se.preserveUrl ? q.get().url : this.pageUrl(i), this.requestParams.all().onBeforeUpdate(i), eb(i), q.set(i, { + replace: this.requestParams.all().replace, + preserveScroll: this.requestParams.all().preserveScroll, + preserveState: this.requestParams.all().preserveState, + viewTransition: this.requestParams.all().viewTransition + })) : Promise.resolve(); + } + getDataFromResponse(i) { + if (typeof i != "string") + return i; + try { + return JSON.parse(i); + } catch { + return i; + } + } + shouldSetPage(i) { + if (!this.requestParams.all().async || this.originatingPage.component !== i.component) + return !0; + if (this.originatingPage.component !== q.get().component) + return !1; + const s = tn(this.originatingPage.url), c = tn(q.get().url); + return s.origin === c.origin && s.pathname === c.pathname; + } + pageUrl(i) { + const s = tn(i.url); + return i.preserveFragment ? s.hash = this.requestParams.all().url.hash : rp(this.requestParams.all().url, s), s.pathname + s.search + s.hash; + } + preserveOptimisticProps(i) { + if (Ze.hasPendingOptimistic()) + for (const s of Object.keys(i.props)) + q.hasBaseline(s) && (q.updateBaseline(s, i.props[s]), i.props[s] = q.get().props[s]); + } + preserveEqualProps(i) { + if (i.component !== q.get().component) + return; + const s = q.get().props; + Object.entries(i.props).forEach(([c, o]) => { + _i(o, s[c]) && (i.props[c] = s[c]); + }); + } + mergeProps(i) { + if (!this.requestParams.isPartial() || i.component !== q.get().component) + return; + const s = i.mergeProps || [], c = i.prependProps || [], o = i.deepMergeProps || [], p = i.matchPropsOn || [], d = (S, A) => { + const w = mt(q.get().props, S), x = mt(i.props, S); + if (Array.isArray(x)) { + const _ = this.mergeOrMatchItems( + w || [], + x, + S, + p, + A + ); + _n(i.props, S, _); + } else if (typeof x == "object" && x !== null) { + const _ = { + ...w || {}, + ...x + }; + _n(i.props, S, _); + } + }; + s.forEach((S) => d(S, !0)), c.forEach((S) => d(S, !1)), o.forEach((S) => { + const A = mt(q.get().props, S), w = mt(i.props, S), x = (_, B, Z) => Array.isArray(B) ? this.mergeOrMatchItems(_, B, Z, p) : typeof B == "object" && B !== null ? Object.keys(B).reduce( + (oe, C) => (oe[C] = x(_ ? _[C] : void 0, B[C], `${Z}.${C}`), oe), + { ..._ } + ) : B; + _n(i.props, S, x(A, w, S)); + }); + const m = new Set( + [...this.requestParams.all().only, ...this.requestParams.all().except].filter((S) => S.includes(".")).map((S) => S.split(".")[0]) + ); + for (const S of m) { + const A = q.get().props[S]; + this.isObject(A) && this.isObject(i.props[S]) && (i.props[S] = this.deepMergeObjects(A, i.props[S])); + } + i.props = { ...q.get().props, ...i.props }, this.shouldPreserveErrors(i) && (i.props.errors = q.get().props.errors), q.get().scrollProps && (i.scrollProps = { + ...q.get().scrollProps || {}, + ...i.scrollProps || {} + }), q.hasOnceProps() && (i.onceProps = { + ...q.get().onceProps || {}, + ...i.onceProps || {} + }), this.requestParams.isDeferredPropsRequest() && (i.flash = { ...q.get().flash }); + const g = q.get().initialDeferredProps; + g && Object.keys(g).length > 0 && (i.initialDeferredProps = g); + } + /** + * By default, the Laravel adapter shares validation errors via Inertia::always(), + * so responses always include errors, even when empty. Components like + * InfiniteScroll and WhenVisible, as well as loading deferred props, + * perform async requests that should practically never reset errors. + */ + shouldPreserveErrors(i) { + if (!this.requestParams.all().preserveErrors) + return !1; + const s = q.get().props.errors; + if (!s || Object.keys(s).length === 0) + return !1; + const c = i.props.errors; + return !(c && Object.keys(c).length > 0); + } + isObject(i) { + return i && typeof i == "object" && !Array.isArray(i); + } + deepMergeObjects(i, s) { + const c = { ...i }; + for (const o of Object.keys(s)) { + const p = i[o], d = s[o]; + this.isObject(p) && this.isObject(d) ? c[o] = this.deepMergeObjects(p, d) : c[o] = d; + } + return c; + } + mergeOrMatchItems(i, s, c, o, p = !0) { + const d = Array.isArray(i) ? i : [], m = o.find((A) => A.split(".").slice(0, -1).join(".") === c); + if (!m) + return p ? [...d, ...s] : [...s, ...d]; + const g = m.split(".").pop() || "", S = /* @__PURE__ */ new Map(); + return s.forEach((A) => { + this.hasUniqueProperty(A, g) && S.set(A[g], A); + }), p ? this.appendWithMatching(d, s, S, g) : this.prependWithMatching(d, s, S, g); + } + appendWithMatching(i, s, c, o) { + const p = i.map((m) => this.hasUniqueProperty(m, o) && c.has(m[o]) ? c.get(m[o]) : m), d = s.filter((m) => this.hasUniqueProperty(m, o) ? !i.some( + (g) => this.hasUniqueProperty(g, o) && g[o] === m[o] + ) : !0); + return [...p, ...d]; + } + prependWithMatching(i, s, c, o) { + const p = i.filter((d) => this.hasUniqueProperty(d, o) ? !c.has(d[o]) : !0); + return [...s, ...p]; + } + hasUniqueProperty(i, s) { + return i && typeof i == "object" && s in i; + } + async setRememberedState(i) { + const s = await Se.getState(Se.rememberedState, {}); + this.requestParams.all().preserveState && s && i.component === q.get().component && (i.rememberedState = s); + } + getScopedErrors(i) { + return this.requestParams.all().errorBag ? i[this.requestParams.all().errorBag || ""] || {} : i; + } +}, fp = class Xy { + constructor(i, s, { optimistic: c = !1 } = {}) { + this.page = s, this.requestParams = ys.create(i), this.cancelToken = new AbortController(), this.optimistic = c; + } + response; + cancelToken; + requestParams; + requestHasFinished = !1; + optimistic; + static create(i, s, c) { + return new Xy(i, s, c); + } + isPrefetch() { + return this.requestParams.isPrefetch(); + } + isOptimistic() { + return this.optimistic; + } + isPendingOptimistic() { + return this.isOptimistic() && (!this.response || !this.response.isProcessed()); + } + async send() { + this.requestParams.onCancelToken(() => this.cancel({ cancelled: !0 })), nb(this.requestParams.all()), this.requestParams.onStart(), this.requestParams.all().prefetch && (this.requestParams.onPrefetching(), ib(this.requestParams.all())); + const i = this.requestParams.all().prefetch; + return Cy.getClient().request({ + method: this.requestParams.all().method, + url: qs(this.requestParams.all().url).href, + data: this.requestParams.data(), + signal: this.cancelToken.signal, + headers: this.getHeaders(), + onUploadProgress: this.onProgress.bind(this) + }).then((s) => (this.response = cp.create(this.requestParams, s, this.page), this.response.handle())).catch((s) => s instanceof zf ? (this.response = cp.create(this.requestParams, s.response, this.page), this.response.handle()) : Promise.reject(s)).catch((s) => { + if (!(s instanceof Df) && this.requestParams.all().onNetworkError(s) !== !1 && kS(s)) + return i && this.requestParams.onPrefetchError(s), Promise.reject(s); + }).finally(() => { + this.finish(), i && this.response && this.requestParams.onPrefetchResponse(this.response); + }); + } + finish() { + this.requestParams.wasCancelledAtAll() || (this.requestParams.markAsFinished(), this.fireFinishEvents()); + } + fireFinishEvents() { + this.requestHasFinished || (this.requestHasFinished = !0, IS(this.requestParams.all()), this.requestParams.onFinish()); + } + cancel({ cancelled: i = !1, interrupted: s = !1 }) { + this.requestHasFinished || (this.cancelToken.abort(), this.requestParams.markAsCancelled({ cancelled: i, interrupted: s }), this.fireFinishEvents()); + } + onProgress(i) { + this.requestParams.data() instanceof FormData && (tb(i), this.requestParams.all().onProgress(i)); + } + getHeaders() { + const i = { + ...this.requestParams.headers(), + Accept: "text/html, application/xhtml+xml", + "X-Requested-With": "XMLHttpRequest", + "X-Inertia": !0 + }, s = q.get(); + s.version && (i["X-Inertia-Version"] = s.version); + const c = Object.entries(s.onceProps || {}).filter(([, o]) => mt(s.props, o.prop) === void 0 ? !1 : !o.expiresAt || o.expiresAt > Date.now()).map(([o]) => o); + return c.length > 0 && (i["X-Inertia-Except-Once-Props"] = c.join(",")), i; + } +}, op = class { + requests = []; + maxConcurrent; + interruptible; + constructor({ maxConcurrent: l, interruptible: i }) { + this.maxConcurrent = l, this.interruptible = i; + } + send(l) { + this.requests.push(l), l.send().finally(() => { + this.requests = this.requests.filter((i) => i !== l); + }); + } + interruptInFlight() { + this.cancel({ interrupted: !0 }, !1); + } + cancelInFlight({ prefetch: l = !0, optimistic: i = !0 } = {}) { + this.requests.filter((s) => l || !s.isPrefetch()).filter((s) => i || !s.isOptimistic()).forEach((s) => s.cancel({ cancelled: !0 })); + } + cancel({ cancelled: l = !1, interrupted: i = !1 } = {}, s = !1) { + if (!s && !this.shouldCancel()) + return; + this.requests.shift()?.cancel({ cancelled: l, interrupted: i }); + } + shouldCancel() { + return this.interruptible && this.requests.length >= this.maxConcurrent; + } + hasPendingOptimistic() { + return this.requests.some((l) => l.isPendingOptimistic()); + } +}, Dt = () => { +}, Zb = class { + syncRequestStream = new op({ + maxConcurrent: 1, + interruptible: !0 + }); + asyncRequestStream = new op({ + maxConcurrent: 1 / 0, + interruptible: !1 + }); + clientVisitQueue = new Qs(); + pendingOptimisticCallback = void 0; + init({ + initialPage: l, + resolveComponent: i, + swapComponent: s, + onFlash: c + }) { + q.init({ + initialPage: l, + resolveComponent: i, + swapComponent: s, + onFlash: c + }), xb.handle(), yn.init(), yn.on("missingHistoryItem", () => { + typeof window < "u" && this.visit(window.location.href, { preserveState: !0, preserveScroll: !0, replace: !0 }); + }), yn.on("loadDeferredProps", (o) => { + this.loadDeferredProps(o); + }), yn.on("historyQuotaExceeded", (o) => { + window.location.href = o; + }); + } + optimistic(l) { + return this.pendingOptimisticCallback = l, this; + } + get(l, i = {}, s = {}) { + return this.visit(l, { ...s, method: "get", data: i }); + } + post(l, i = {}, s = {}) { + return this.visit(l, { preserveState: !0, ...s, method: "post", data: i }); + } + put(l, i = {}, s = {}) { + return this.visit(l, { preserveState: !0, ...s, method: "put", data: i }); + } + patch(l, i = {}, s = {}) { + return this.visit(l, { preserveState: !0, ...s, method: "patch", data: i }); + } + delete(l, i = {}) { + return this.visit(l, { preserveState: !0, ...i, method: "delete" }); + } + reload(l = {}) { + return this.doReload(l); + } + doReload(l = {}) { + if (!(typeof window > "u")) + return this.visit(window.location.href, { + ...l, + preserveScroll: !0, + preserveState: !0, + async: !0, + headers: { + ...l.headers || {}, + "Cache-Control": "no-cache" + } + }); + } + remember(l, i = "default") { + Se.remember(l, i); + } + restore(l = "default") { + return Se.restore(l); + } + on(l, i) { + return typeof window > "u" ? () => { + } : yn.onGlobalEvent(l, i); + } + hasPendingOptimistic() { + return this.asyncRequestStream.hasPendingOptimistic(); + } + cancelAll({ async: l = !0, prefetch: i = !0, sync: s = !0 } = {}) { + l && this.asyncRequestStream.cancelInFlight({ prefetch: i }), s && this.syncRequestStream.cancelInFlight(); + } + poll(l, i = {}, s = {}) { + return jb.add(l, () => this.reload({ preserveErrors: !0, ...i }), { + autoStart: s.autoStart ?? !0, + keepAlive: s.keepAlive ?? !1 + }); + } + visit(l, i = {}) { + i.optimistic = i.optimistic ?? this.pendingOptimisticCallback, this.pendingOptimisticCallback = void 0, i.optimistic && (i.async = i.async ?? !0); + const s = this.getPendingVisit(l, { + ...i, + showProgress: i.showProgress ?? (!i.async || !!i.optimistic) + }), c = this.getVisitEvents(i); + if (c.onBefore(s) === !1 || !np(s)) + return; + const o = tn(q.get().url); + (s.only.length > 0 || s.except.length > 0 || s.reset.length > 0 ? Rb(s.url, o) : js(s.url, o)) || this.asyncRequestStream.cancelInFlight({ prefetch: !1, optimistic: !1 }), s.async || this.syncRequestStream.interruptInFlight(), i.optimistic && this.applyOptimisticUpdate(i.optimistic, c), !q.isCleared() && !s.preserveUrl && Tt.save(); + const m = { + ...s, + ...c + }, g = () => { + const S = pn.get(m); + S ? (uf.reveal(S.inFlight), pn.use(S, m)) : (uf.reveal(!0), (s.async ? this.asyncRequestStream : this.syncRequestStream).send(fp.create(m, q.get(), { optimistic: !!i.optimistic }))); + }; + Array.isArray(s.component) && (console.error( + `The "component" prop received an array of components (${s.component.join(", ")}), but only a single component string is supported for instant visits. Pass an explicit component name instead.` + ), s.component = null), s.component ? Se.processQueue().then(() => { + this.performInstantSwap(s).then(() => { + m.preserveState = !0, m.replace = !0, m.viewTransition = !1, g(); + }); + }) : g(); + } + getCached(l, i = {}) { + return pn.findCached(this.getPrefetchParams(l, i)); + } + flush(l, i = {}) { + pn.remove(this.getPrefetchParams(l, i)); + } + flushAll() { + pn.removeAll(); + } + flushByCacheTags(l) { + pn.removeByTags(Array.isArray(l) ? l : [l]); + } + getPrefetching(l, i = {}) { + return pn.findInFlight(this.getPrefetchParams(l, i)); + } + prefetch(l, i = {}, s = {}) { + if ((i.method ?? (un(l) ? l.method : "get")) !== "get") + throw new Error("Prefetch requests must use the GET method"); + const o = this.getPendingVisit(l, { + ...i, + async: !0, + showProgress: !1, + prefetch: !0, + viewTransition: !1 + }), p = o.url.origin + o.url.pathname + o.url.search, d = window.location.origin + window.location.pathname + window.location.search; + if (p === d) + return; + const m = this.getVisitEvents(i); + if (m.onBefore(o) === !1 || !np(o)) + return; + uf.hide(), this.asyncRequestStream.interruptInFlight(); + const g = { + ...o, + ...m + }; + new Promise((A) => { + const w = () => { + q.get() ? A() : setTimeout(w, 50); + }; + w(); + }).then(() => { + pn.add( + g, + (A) => { + this.asyncRequestStream.send(fp.create(A, q.get())); + }, + { + cacheFor: Gi.get("prefetch.cacheFor"), + cacheTags: [], + ...s + } + ); + }); + } + clearHistory() { + Se.clear(); + } + decryptHistory() { + return Se.decrypt(); + } + resolveComponent(l, i) { + return q.resolve(l, i); + } + replace(l) { + this.clientVisit(l, { replace: !0 }); + } + replaceProp(l, i, s) { + this.replace({ + preserveScroll: !0, + preserveState: !0, + props(c) { + const o = typeof i == "function" ? i(mt(c, l), c) : i; + return _n(sn(c), l, o); + }, + ...s || {} + }); + } + appendToProp(l, i, s) { + this.replaceProp( + l, + (c, o) => { + const p = typeof i == "function" ? i(c, o) : i; + return Array.isArray(c) || (c = c !== void 0 ? [c] : []), [...c, p]; + }, + s + ); + } + prependToProp(l, i, s) { + this.replaceProp( + l, + (c, o) => { + const p = typeof i == "function" ? i(c, o) : i; + return Array.isArray(c) || (c = c !== void 0 ? [c] : []), [p, ...c]; + }, + s + ); + } + push(l) { + this.clientVisit(l); + } + flash(l, i) { + const s = q.get().flash; + let c; + if (typeof l == "function") + c = l(s); + else if (typeof l == "string") + c = { ...s, [l]: i }; + else if (l && Object.keys(l).length) + c = { ...s, ...l }; + else + return; + q.setFlash(c), Object.keys(c).length && Ns(c); + } + clientVisit(l, { replace: i = !1 } = {}) { + this.clientVisitQueue.add(() => this.performClientVisit(l, { replace: i })); + } + performClientVisit(l, { replace: i = !1 } = {}) { + const s = q.get(), c = typeof l.props == "function" ? Object.fromEntries( + Object.values(s.onceProps ?? {}).map((Z) => [ + Z.prop, + mt(s.props, Z.prop) + ]) + ) : {}, o = typeof l.props == "function" ? l.props(s.props, c) : l.props ?? s.props, p = typeof l.flash == "function" ? l.flash(s.flash) : l.flash, { viewTransition: d, onError: m, onFinish: g, onFlash: S, onSuccess: A, ...w } = l, x = { + ...s, + ...w, + flash: p ?? {}, + props: o + }, _ = ys.resolvePreserveOption(l.preserveScroll ?? !1, x), B = ys.resolvePreserveOption(l.preserveState ?? !1, x); + return q.set(x, { + replace: i, + preserveScroll: _, + preserveState: B, + viewTransition: d + }).then(() => { + const Z = q.get().flash; + Object.keys(Z).length > 0 && (Ns(Z), S?.(Z)); + const oe = q.get().props.errors || {}; + if (Object.keys(oe).length === 0) { + A?.(q.get()); + return; + } + const C = l.errorBag ? oe[l.errorBag || ""] || {} : oe; + m?.(C); + }).finally(() => g?.(l)); + } + performInstantSwap(l) { + const i = q.get(), s = Object.fromEntries( + (i.sharedProps ?? []).filter((d) => d in i.props).map((d) => [d, i.props[d]]) + ), c = typeof l.pageProps == "function" ? l.pageProps(sn(i.props), sn(s)) : l.pageProps, o = c !== null ? { ...c } : { ...s }, p = { + component: l.component, + url: l.url.pathname + l.url.search + l.url.hash, + version: i.version, + props: { + ...o, + errors: {} + }, + flash: {}, + clearHistory: !1, + encryptHistory: i.encryptHistory, + sharedProps: i.sharedProps, + rememberedState: {} + }; + return q.set(p, { + replace: l.replace, + preserveScroll: ys.resolvePreserveOption(l.preserveScroll, p), + preserveState: !1, + viewTransition: l.viewTransition + }); + } + getPrefetchParams(l, i) { + return { + ...this.getPendingVisit(l, { + ...i, + async: !0, + showProgress: !1, + prefetch: !0, + viewTransition: !1 + }), + ...this.getVisitEvents(i) + }; + } + getPendingVisit(l, i) { + if (un(l)) { + const g = l; + l = g.url, i.method = i.method ?? g.method; + } + const s = Gi.get("visitOptions"), c = s ? s(l.toString(), sn(i)) || {} : {}, o = { + method: "get", + data: {}, + replace: !1, + preserveScroll: !1, + preserveState: !1, + only: [], + except: [], + headers: {}, + errorBag: "", + forceFormData: !1, + queryStringArrayFormat: "brackets", + async: !1, + showProgress: !0, + fresh: !1, + reset: [], + preserveUrl: !1, + preserveErrors: !1, + prefetch: !1, + invalidateCacheTags: [], + viewTransition: !1, + component: null, + pageProps: null, + ...i, + ...c + }, [p, d] = Ob( + l, + o.data, + o.method, + o.forceFormData, + o.queryStringArrayFormat + ), m = { + cancelled: !1, + completed: !1, + interrupted: !1, + ...o, + url: p, + data: d + }; + return m.prefetch && (m.headers.Purpose = "prefetch"), m; + } + getVisitEvents(l) { + return { + onCancelToken: l.onCancelToken || Dt, + onBefore: l.onBefore || Dt, + onBeforeUpdate: l.onBeforeUpdate || Dt, + onStart: l.onStart || Dt, + onProgress: l.onProgress || Dt, + onFinish: l.onFinish || Dt, + onCancel: l.onCancel || Dt, + onSuccess: l.onSuccess || Dt, + onError: l.onError || Dt, + onHttpException: l.onHttpException || Dt, + onNetworkError: l.onNetworkError || Dt, + onFlash: l.onFlash || Dt, + onPrefetched: l.onPrefetched || Dt, + onPrefetching: l.onPrefetching || Dt + }; + } + applyOptimisticUpdate(l, i) { + const s = q.get().props, c = l(sn(s)); + if (!c) + return; + const o = []; + for (const A of Object.keys(c)) + _i(s[A], c[A]) || o.push(A); + if (o.length === 0) + return; + const p = q.nextOptimisticId(), d = q.get().component; + for (const A of o) + q.setBaseline(A, sn(s[A])); + q.registerOptimistic(p, l), q.setPropsQuietly({ ...s, ...c }); + let m = !0; + const g = i.onSuccess; + i.onSuccess = (A) => (m = !1, g(A)); + const S = i.onFinish; + i.onFinish = (A) => { + if (q.unregisterOptimistic(p), m && q.get().component === d) { + const w = q.replayOptimistics(); + Object.keys(w).length > 0 && q.setPropsQuietly({ ...q.get().props, ...w }); + } + return q.pendingOptimisticCount() === 0 && q.clearOptimisticState(), S(A); + }; + } + loadDeferredProps(l) { + l && Object.values(l).forEach((i) => { + this.doReload({ only: i, deferredProps: !0, preserveErrors: !0 }); + }); + } +}, Hs = class { + /** + * Creates a callback that returns a UrlMethodPair. + * + * createWayfinderCallback(urlMethodPair) + * createWayfinderCallback(method, url) + * createWayfinderCallback(() => urlMethodPair) + * createWayfinderCallback(() => method, () => url) + */ + static createWayfinderCallback(...l) { + return () => l.length === 1 ? un(l[0]) ? l[0] : l[0]() : { + method: typeof l[0] == "function" ? l[0]() : l[0], + url: typeof l[1] == "function" ? l[1]() : l[1] + }; + } + /** + * Parses all useForm() arguments into { rememberKey, data, precognitionEndpoint }. + * + * useForm() + * useForm(data) + * useForm(rememberKey, data) + * useForm(method, url, data) + * useForm(urlMethodPair, data) + * + */ + static parseUseFormArguments(...l) { + return l.length === 0 ? { + rememberKey: null, + data: {}, + precognitionEndpoint: null + } : l.length === 1 ? { + rememberKey: null, + data: l[0], + precognitionEndpoint: null + } : l.length === 2 ? typeof l[0] == "string" ? { + rememberKey: l[0], + data: l[1], + precognitionEndpoint: null + } : { + rememberKey: null, + data: l[1], + precognitionEndpoint: this.createWayfinderCallback(l[0]) + } : { + rememberKey: null, + data: l[2], + precognitionEndpoint: this.createWayfinderCallback(l[0], l[1]) + }; + } + /** + * Parses all submission arguments into { method, url, options }. + * It uses the Precognition endpoint if no explicit method/url are provided. + * + * form.submit(method, url) + * form.submit(method, url, options) + * form.submit(urlMethodPair) + * form.submit(urlMethodPair, options) + * form.submit() + * form.submit(options) + */ + static parseSubmitArguments(l, i) { + return l.length === 3 || l.length === 2 && typeof l[0] == "string" ? { method: l[0], url: l[1], options: l[2] ?? {} } : un(l[0]) ? { ...l[0], options: l[1] ?? {} } : { ...i(), options: l[0] ?? {} }; + } + /** + * Merges headers into the Precognition validate() arguments. + */ + static mergeHeadersForValidation(l, i, s) { + const c = (o) => (o.headers = { + ...s ?? {}, + ...o.headers ?? {} + }, o); + return l && typeof l == "object" && !("target" in l) ? l = c(l) : i && typeof i == "object" ? i = c(i) : typeof l == "string" ? i = c(i ?? {}) : l = c(l ?? {}), [l, i]; + } +}; +function Vb(l) { + if (!l.includes(".")) + return l; + const i = (s) => s.startsWith("[") && s.endsWith("]") ? s : s.split(".").reduce((c, o, p) => p === 0 ? o : `${c}[${o}]`); + return l.replace(/\\\./g, "__ESCAPED_DOT__").split(/(\[[^\]]*\])/).filter(Boolean).map(i).join("").replace(/__ESCAPED_DOT__/g, "."); +} +function Kb(l) { + const i = [], s = /([^\[\]]+)|\[(\d*)\]/g; + let c; + for (; (c = s.exec(l)) !== null; ) + c[1] !== void 0 ? i.push(c[1]) : c[2] !== void 0 && i.push(c[2] === "" ? "" : Number(c[2])); + return i; +} +function Pb(l, i, s) { + let c = l; + for (let o = 0; o < i.length - 1; o++) + i[o] in c || (c[i[o]] = {}), c = c[i[o]]; + c[i[i.length - 1]] = s; +} +function Jb(l) { + const i = Object.keys(l), s = i.filter((c) => /^\d+$/.test(c)).map(Number).sort((c, o) => c - o); + return i.length === s.length && s.length > 0 && s[0] === 0 && s.every((c, o) => c === o); +} +function vs(l) { + if (Array.isArray(l)) + return l.map(vs); + if (typeof l != "object" || l === null || Qf(l)) + return l; + if (Jb(l)) { + const s = []; + for (let c = 0; c < Object.keys(l).length; c++) + s[c] = vs(l[c]); + return s; + } + const i = {}; + for (const s in l) + i[s] = vs(l[s]); + return i; +} +function dp(l) { + const i = {}; + for (const [s, c] of l.entries()) { + if (c instanceof File && c.size === 0 && c.name === "") + continue; + const o = Kb(Vb(s)); + if (o[o.length - 1] === "") { + const p = o.slice(0, -1), d = mt(i, p); + if (Array.isArray(d)) + d.push(c); + else if (d && typeof d == "object" && !Qf(d)) { + const m = Object.keys(d).filter((g) => /^\d+$/.test(g)).map(Number).sort((g, S) => g - S); + _n(i, p, m.length > 0 ? [...m.map((g) => d[g]), c] : [c]); + } else + _n(i, p, [c]); + continue; + } + Pb(i, o.map(String), c); + } + return vs(i); +} +var Fb = "X-Inertia-Infinite-Scroll-Merge-Intent", $b = (l) => { + const i = () => { + const C = q.get().scrollProps?.[l.getPropName()]; + if (C) + return C; + throw new Error(`The page object does not contain a scroll prop named "${l.getPropName()}".`); + }, s = { + component: null, + loading: !1, + previousPage: null, + nextPage: null, + lastLoadedPage: null, + requestCount: 0 + }, c = () => { + const C = i(); + s.component = q.get().component, s.loading = !1, s.previousPage = C.previousPage, s.nextPage = C.nextPage, s.lastLoadedPage = C.currentPage, s.requestCount = 0; + }, o = () => `inertia:infinite-scroll-data:${l.getPropName()}`; + if (typeof window < "u") { + c(); + const C = Ze.restore(o()); + C && typeof C == "object" && C.lastLoadedPage === i().currentPage && (s.previousPage = C.previousPage, s.nextPage = C.nextPage, s.lastLoadedPage = C.lastLoadedPage, s.requestCount = C.requestCount || 0); + } + const p = Ze.on("success", (C) => { + s.component === C.detail.page.component && i().reset && (c(), l.onReset?.()); + }), d = (C) => C === "next" ? "nextPage" : "previousPage", m = (C) => { + const ce = d(C); + return s[ce]; + }, g = (C) => { + const ce = i(), me = d(C); + s.lastLoadedPage = ce.currentPage, s[me] = ce[me], s.requestCount += 1, Ze.remember( + { + previousPage: s.previousPage, + nextPage: s.nextPage, + lastLoadedPage: s.lastLoadedPage, + requestCount: s.requestCount + }, + o() + ); + }, S = () => i().pageName, A = () => s.requestCount, w = (C, ce = {}) => { + const me = m(C); + s.loading || me === null || (s.loading = !0, Ze.reload({ + preserveErrors: !0, + ...ce, + data: { ...ce.data || {}, [S()]: me }, + only: [...ce.only || [], l.getPropName()], + preserveUrl: !0, + // we handle URL updates manually via useInfiniteScrollQueryString() + headers: { + [Fb]: C === "previous" ? "prepend" : "append", + ...ce.headers + }, + onBefore: (fe) => { + C === "next" ? l.onBeforeNextRequest() : l.onBeforePreviousRequest(), ce.onBefore?.(fe); + }, + onBeforeUpdate: (fe) => { + l.onBeforeUpdate(), ce.onBeforeUpdate?.(fe); + }, + onSuccess: (fe) => { + g(C), ce.onSuccess?.(fe); + }, + onFinish: (fe) => { + s.loading = !1, C === "next" ? l.onCompleteNextRequest(s.lastLoadedPage) : l.onCompletePreviousRequest(s.lastLoadedPage), ce.onFinish?.(fe); + } + })); + }; + return { + getLastLoadedPage: () => s.lastLoadedPage, + getPageName: S, + getRequestCount: A, + hasPrevious: () => !!s.previousPage, + hasNext: () => !!s.nextPage, + fetchNext: (C) => w("next", C), + fetchPrevious: (C) => w("previous", C), + removeEventListener: p + }; +}, Wb = () => { + const l = []; + return { + new: (c, o = {}) => { + const p = new IntersectionObserver((d) => { + for (const m of d) + m.isIntersecting && c(m); + }, o); + return l.push(p), p; + }, + flushAll: () => { + l.forEach((c) => c.disconnect()), l.length = 0; + } + }; +}, Ss = "infiniteScrollPage", sf = "infiniteScrollIgnore", Yy = (l) => l.dataset[Ss], kb = (l) => { + const i = Wb(); + let s, c, o, p, d = !1; + const m = () => { + p = new MutationObserver((I) => { + I.forEach((ne) => { + ne.addedNodes.forEach((ue) => { + ue.nodeType === Node.ELEMENT_NODE && x.add(ue); + }); + }), fe(); + }), p.observe(l.getItemsElement(), { childList: !0 }), s = i.new( + (I) => l.onItemIntersected(I.target) + ); + const K = { + root: l.getScrollableParent(), + rootMargin: `${Math.max(1, l.getTriggerMargin())}px` + }; + c = i.new(l.onPreviousTriggered, K), o = i.new(l.onNextTriggered, K); + }, g = () => { + d && S(); + const K = l.getStartElement(), I = l.getEndElement(); + K && l.shouldFetchPrevious() && c.observe(K), I && l.shouldFetchNext() && o.observe(I), d = !0; + }, S = () => { + d && (c.disconnect(), o.disconnect(), d = !1); + }, A = () => { + d && g(); + }, w = () => { + S(), i.flushAll(), p?.disconnect(); + }, x = /* @__PURE__ */ new Set(), _ = (K) => !(Ss in K.dataset) && !(sf in K.dataset), B = () => { + Array.from(x).forEach((K) => { + _(K) && (K.dataset[sf] = "true"), s.observe(K); + }), x.clear(); + }, Z = (K) => Array.from( + K.querySelectorAll( + ":scope > *:not([data-infinite-scroll-page]):not([data-infinite-scroll-ignore])" + ) + ); + let oe = !1; + const C = (K) => { + !oe && (oe = !0, Be()) || (Z(l.getItemsElement()).forEach((I) => { + _(I) && (I.dataset[Ss] = K?.toString() || "1"), s.observe(I); + }), me()); + }, ce = () => `inertia:infinite-scroll-elements:${l.getPropName()}`, me = () => { + const K = {}, I = l.getItemsElement().childNodes; + for (let ne = 0; ne < I.length; ne++) { + const ue = I[ne]; + if (ue.nodeType !== Node.ELEMENT_NODE) + continue; + const V = Yy(ue); + typeof V > "u" || (V in K ? K[V].to = ne : K[V] = { from: ne, to: ne }); + } + Ze.remember(K, ce()); + }, fe = Us(me, 250), Be = () => { + const K = Ze.restore(ce()); + if (!K || typeof K != "object") + return !1; + const I = l.getItemsElement().childNodes; + for (let ne = 0; ne < I.length; ne++) { + const ue = I[ne]; + if (ue.nodeType !== Node.ELEMENT_NODE) + continue; + const V = ue; + let j; + for (const [H, Y] of Object.entries(K)) + if (ne >= Y.from && ne <= Y.to) { + j = H; + break; + } + if (j) + V.dataset[Ss] = j; + else if (_(V)) + V.dataset[sf] = "true"; + else + continue; + s.observe(V); + } + return !0; + }; + return { + setupObservers: m, + enableTriggers: g, + disableTriggers: S, + refreshTriggers: A, + flushAll: w, + processManuallyAddedElements: B, + processServerLoadedElements: C + }; +}, Ib = new Qs(), Ea, fl, rs = null, e1 = (l) => { + let i = !0; + const s = (o) => { + Ib.add(() => new Promise((p) => { + if (!i) + return Ea = fl = null, p(); + if (!Ea || !fl) { + const g = q.get().url; + Ea = tn(g), fl = tn(g), rs = By(g); + } + const d = l.getPageName(), m = fl.searchParams; + o === "1" ? m.delete(d) : m.set(d, o), setTimeout(() => p()); + })).finally(() => { + i && Ea && fl && Ea.href !== fl.href && rs !== null && Ze.replace({ + url: zb(fl, rs), + preserveScroll: !0, + preserveState: !0 + }), Ea = fl = rs = null; + }); + }; + return { + onItemIntersected: Us((o) => { + const p = l.getItemsElement(); + if (!i || l.shouldPreserveUrl() || !o || !p) + return; + const d = /* @__PURE__ */ new Map(), m = [...p.children]; + xy(m, o).forEach((A) => { + const w = Yy(A) ?? "1"; + d.has(w) ? d.set(w, d.get(w) + 1) : d.set(w, 1); + }); + const S = Array.from(d.entries()).sort((A, w) => w[1] - A[1])[0]?.[0]; + S !== void 0 && s(S); + }, 250), + cancel: () => i = !1 + }; +}, t1 = (l) => ({ + createCallbacks: () => { + let s, c = null, o = 0; + return { + captureScrollPosition: () => { + const m = l.getScrollableParent(), g = l.getItemsElement(); + s = m?.scrollTop || window.scrollY; + const S = xy([...g.children]); + if (S.length > 0) { + c = S[0]; + const A = m?.getBoundingClientRect() || { top: 0 }, w = m ? A.top : 0; + o = c.getBoundingClientRect().top - w; + } + }, + restoreScrollPosition: () => { + if (!c) + return; + let m = 0, g = !1; + const S = () => { + if (m++, g || m > 10) + return !1; + const A = l.getScrollableParent(), w = A?.getBoundingClientRect() || { top: 0 }, x = A ? w.top : 0, Z = c.getBoundingClientRect().top - x - o; + if (Z === 0) { + window.requestAnimationFrame(S); + return; + } + A ? A.scrollTo({ top: s + Z }) : window.scrollTo(0, window.scrollY + Z), g = !0; + }; + window.requestAnimationFrame(S); + } + }; + } +}); +function n1(l) { + const i = e1({ ...l, getPageName: () => o.getPageName() }), s = t1(l), c = kb({ + ...l, + // As items enter viewport, update URL to reflect the most visible page + onItemIntersected: i.onItemIntersected, + onPreviousTriggered: () => o.fetchPrevious(), + onNextTriggered: () => o.fetchNext() + }), o = $b({ + ...l, + // Before updating page data, tag any manually added DOM elements + // so they don't get confused with server-loaded content + onBeforeUpdate: c.processManuallyAddedElements, + // After successful request, tag new server content + onCompletePreviousRequest: (S) => { + l.onCompletePreviousRequest(), Xi(() => c.processServerLoadedElements(S), 2); + }, + onCompleteNextRequest: (S) => { + l.onCompleteNextRequest(), Xi(() => c.processServerLoadedElements(S), 2); + }, + onReset: l.onDataReset + }), p = (S) => { + const { captureScrollPosition: A, restoreScrollPosition: w } = s.createCallbacks(), x = S.onBeforeUpdate || (() => { + }), _ = S.onSuccess || (() => { + }); + return S.onBeforeUpdate = (B) => { + x(B), A(); + }, S.onSuccess = (B) => { + _(B), w(); + }, S; + }, d = o.fetchNext; + o.fetchNext = (S = {}) => { + S = { ...l.getReloadOptions?.(), ...S }, l.inReverseMode() && (S = p(S)), d(S); + }; + const m = o.fetchPrevious; + o.fetchPrevious = (S = {}) => { + S = { ...l.getReloadOptions?.(), ...S }, l.inReverseMode() || (S = p(S)), m(S); + }; + const g = Ze.on("success", () => Xi(c.refreshTriggers, 2)); + return { + dataManager: o, + elementManager: c, + flush: () => { + g(), o.removeEventListener(), c.flushAll(), i.cancel(); + } + }; +} +function Qy(l) { + return l.target instanceof HTMLElement && l.target.isContentEditable || l.defaultPrevented; +} +function ss(l) { + const i = l.currentTarget.tagName.toLowerCase() === "a"; + return !(Qy(l) || i && l.altKey || i && l.ctrlKey || i && l.metaKey || i && l.shiftKey || i && "button" in l && l.button !== 0); +} +function hp(l) { + const i = l.currentTarget.tagName.toLowerCase() === "button"; + return !Qy(l) && (l.key === "Enter" || i && l.key === " "); +} +var et = "nprogress", jl, tt, vt = { + minimum: 0.08, + easing: "linear", + speed: 200, + trickle: !0, + trickleSpeed: 200, + showSpinner: !0, + barSelector: '[role="bar"]', + spinnerSelector: '[role="spinner"]', + parent: "body", + color: "#29d", + includeCSS: !0, + popover: null, + template: [ + '
', + '
', + "
", + '
', + '
', + "
" + ].join("") +}, hl = null, Bs = !1, l1 = (l) => { + Object.assign(vt, l), jl = vt.popover ?? "popover" in HTMLElement.prototype, vt.includeCSS && u1(vt.color), tt = document.createElement("div"), tt.id = et, tt.innerHTML = vt.template, jl && (tt.popover = "manual"); +}, Gs = (l) => { + const i = Gy(); + l = Jy(l, vt.minimum, 1), hl = l === 1 ? null : l; + const s = i1(!i), c = s.querySelector(vt.barSelector), o = vt.speed, p = vt.easing; + s.offsetWidth, s1((d) => { + const m = { + transition: `all ${o}ms ${p}`, + transform: `translate3d(${Fy(l)}%,0,0)` + }; + for (const g in m) + c.style[g] = m[g]; + if (l !== 1) + return setTimeout(d, o); + s.style.transition = "none", s.style.opacity = "1", s.offsetWidth, setTimeout(() => { + s.style.transition = `all ${o}ms linear`, s.style.opacity = "0", setTimeout(() => { + Py(), s.style.transition = "", s.style.opacity = "", d(); + }, o); + }, o); + }); +}, Gy = () => typeof hl == "number", Zy = () => { + hl || Gs(0); + const l = function() { + setTimeout(function() { + hl && (Vy(), l()); + }, vt.trickleSpeed); + }; + vt.trickle && l(); +}, a1 = (l) => { + !l && !hl || (Vy(0.3 + 0.5 * Math.random()), Gs(1)); +}, Vy = (l) => { + const i = hl; + if (i === null) + return Zy(); + if (!(i > 1)) + return l = typeof l == "number" ? l : (() => { + const s = { + 0.1: [0, 0.2], + 0.04: [0.2, 0.5], + 0.02: [0.5, 0.8], + 5e-3: [0.8, 0.99] + }; + for (const c in s) + if (i >= s[c][0] && i < s[c][1]) + return parseFloat(c); + return 0; + })(), Gs(Jy(i + l, 0, 0.994)); +}, i1 = (l) => { + if (r1()) + return document.getElementById(et); + document.documentElement.classList.add(`${et}-busy`); + const i = tt.querySelector(vt.barSelector), s = l ? "-100" : Fy(hl || 0); + if (i.style.transition = "all 0 linear", i.style.transform = `translate3d(${s}%,0,0)`, vt.showSpinner || tt.querySelector(vt.spinnerSelector)?.remove(), jl) + document.body.appendChild(tt), Bs || tt.showPopover(); + else { + const c = Ky(); + c !== document.body && c.classList.add(`${et}-custom-parent`), c.appendChild(tt), Bs && (tt.style.display = "none"); + } + return tt; +}, Ky = () => document.querySelector(vt.parent), Py = () => { + if (document.documentElement.classList.remove(`${et}-busy`), jl && tt?.isConnected) + try { + tt.hidePopover(); + } catch { + } + jl || Ky().classList.remove(`${et}-custom-parent`), tt?.remove(); +}, r1 = () => document.getElementById(et) !== null; +function Jy(l, i, s) { + return l < i ? i : l > s ? s : l; +} +var Fy = (l) => (-1 + l) * 100, s1 = /* @__PURE__ */ (() => { + const l = [], i = () => { + const s = l.shift(); + s && s(i); + }; + return (s) => { + l.push(s), l.length === 1 && i(); + }; +})(), u1 = (l) => { + const i = document.createElement("style"); + i.textContent = ` + #${et} { + pointer-events: none; + background: none; + border: none; + margin: 0; + padding: 0; + overflow: visible; + inset: unset; + width: 100%; + height: 0; + position: fixed; + top: 0; + left: 0; + } + + #${et}::backdrop { + display: none; + } + + #${et} .bar { + background: ${l}; + + position: fixed; + z-index: 1031; + top: 0; + left: 0; + + width: 100%; + height: 2px; + } + + #${et} .peg { + display: block; + position: absolute; + right: 0px; + width: 100px; + height: 100%; + box-shadow: 0 0 10px ${l}, 0 0 5px ${l}; + opacity: 1.0; + + transform: rotate(3deg) translate(0px, -4px); + } + + #${et} .spinner { + display: block; + position: fixed; + z-index: 1031; + top: 15px; + right: 15px; + } + + #${et} .spinner-icon { + width: 18px; + height: 18px; + box-sizing: border-box; + + border: solid 2px transparent; + border-top-color: ${l}; + border-left-color: ${l}; + border-radius: 50%; + + animation: ${et}-spinner 400ms linear infinite; + } + + .${et}-custom-parent { + overflow: hidden; + position: relative; + } + + .${et}-custom-parent #${et} .spinner, + .${et}-custom-parent #${et} .bar { + position: absolute; + } + + @keyframes ${et}-spinner { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + `, document.head.appendChild(i); +}, c1 = () => { + if (Bs = !1, !!tt?.isConnected) + if (jl) + try { + tt.showPopover(); + } catch { + } + else + tt.style.display = ""; +}, f1 = () => { + if (Bs = !0, !!tt?.isConnected) + if (jl) + try { + tt.hidePopover(); + } catch { + } + else + tt.style.display = "none"; +}, hn = { + configure: l1, + isStarted: Gy, + done: a1, + set: Gs, + remove: Py, + start: Zy, + status: hl, + show: c1, + hide: f1 +}, o1 = class { + hideCount = 0; + start() { + hn.start(); + } + reveal(l = !1) { + this.hideCount = Math.max(0, this.hideCount - 1), (l || this.hideCount === 0) && hn.show(); + } + hide() { + this.hideCount++, hn.hide(); + } + set(l) { + hn.set(Math.max(0, Math.min(1, l))); + } + finish() { + hn.done(); + } + reset() { + hn.set(0); + } + remove() { + hn.done(), hn.remove(); + } + isStarted() { + return hn.isStarted(); + } + getStatus() { + return hn.status; + } +}, uf = new o1(), $y = /* @__PURE__ */ Symbol("FormComponentReset"); +function Mf(l) { + return l instanceof HTMLInputElement || l instanceof HTMLSelectElement || l instanceof HTMLTextAreaElement; +} +function d1(l, i) { + const s = l.value, c = l.checked; + switch (l.type.toLowerCase()) { + case "checkbox": + l.checked = i.includes(l.value); + break; + case "radio": + l.checked = i[0] === l.value; + break; + case "file": + l.value = ""; + break; + case "button": + case "submit": + case "reset": + case "image": + break; + default: + l.value = i[0] !== null && i[0] !== void 0 ? String(i[0]) : ""; + } + return l.value !== s || l.checked !== c; +} +function h1(l, i) { + const s = l.value, c = Array.from(l.selectedOptions).map((d) => d.value); + if (l.multiple) { + const d = i.map((m) => String(m)); + Array.from(l.options).forEach((m) => { + m.selected = d.includes(m.value); + }); + } else + l.value = i[0] !== void 0 ? String(i[0]) : ""; + const o = Array.from(l.selectedOptions).map((d) => d.value); + return l.multiple ? JSON.stringify(c.sort()) !== JSON.stringify(o.sort()) : l.value !== s; +} +function cf(l, i) { + if (l.disabled) { + if (l instanceof HTMLInputElement) { + const s = l.value, c = l.checked; + switch (l.type.toLowerCase()) { + case "checkbox": + case "radio": + return l.checked = l.defaultChecked, l.checked !== c; + case "file": + return l.value = "", s !== ""; + case "button": + case "submit": + case "reset": + case "image": + return !1; + default: + return l.value = l.defaultValue, l.value !== s; + } + } else if (l instanceof HTMLSelectElement) { + const s = Array.from(l.selectedOptions).map((o) => o.value); + Array.from(l.options).forEach((o) => { + o.selected = o.defaultSelected; + }); + const c = Array.from(l.selectedOptions).map((o) => o.value); + return JSON.stringify(s.sort()) !== JSON.stringify(c.sort()); + } else if (l instanceof HTMLTextAreaElement) { + const s = l.value; + return l.value = l.defaultValue, l.value !== s; + } + return !1; + } + if (l instanceof HTMLInputElement) + return d1(l, i); + if (l instanceof HTMLSelectElement) + return h1(l, i); + if (l instanceof HTMLTextAreaElement) { + const s = l.value; + return l.value = i[0] !== void 0 ? String(i[0]) : "", l.value !== s; + } + return !1; +} +function m1(l, i) { + let s = !1; + return l instanceof RadioNodeList || l instanceof HTMLCollection ? Array.from(l).forEach((c, o) => { + if (c instanceof Element && Mf(c)) + if (c instanceof HTMLInputElement && ["checkbox", "radio"].includes(c.type.toLowerCase())) + cf(c, i) && (s = !0); + else { + const p = i[o] !== void 0 ? [i[o]] : [i[0] ?? null].filter(Boolean); + cf(c, p) && (s = !0); + } + }) : Mf(l) && (s = cf(l, i)), s; +} +function p1(l, i, s) { + if (!l) + return; + const c = !s || s.length === 0; + if (c) { + const p = new FormData(l), d = Array.from(l.elements).map((m) => Mf(m) ? m.name : "").filter(Boolean); + s = [.../* @__PURE__ */ new Set([...i.keys(), ...p.keys(), ...d])]; + } + let o = !1; + s.forEach((p) => { + const d = l.elements.namedItem(p); + d && m1(d, i.getAll(p)) && (o = !0); + }), o && c && l.dispatchEvent( + new CustomEvent("reset", { bubbles: !0, cancelable: !0, detail: { [$y]: !0 } }) + ); +} +var Ze = new Zb(); +var ff = { exports: {} }, Di = {}, of = { exports: {} }, df = {}; +var mp; +function y1() { + return mp || (mp = 1, (function(l) { + function i(b, N) { + var L = b.length; + b.push(N); + e: for (; 0 < L; ) { + var P = L - 1 >>> 1, le = b[P]; + if (0 < o(le, N)) + b[P] = N, b[L] = le, L = P; + else break e; + } + } + function s(b) { + return b.length === 0 ? null : b[0]; + } + function c(b) { + if (b.length === 0) return null; + var N = b[0], L = b.pop(); + if (L !== N) { + b[0] = L; + e: for (var P = 0, le = b.length, Ee = le >>> 1; P < Ee; ) { + var J = 2 * (P + 1) - 1, ee = b[J], ae = J + 1, qe = b[ae]; + if (0 > o(ee, L)) + ae < le && 0 > o(qe, ee) ? (b[P] = qe, b[ae] = L, P = ae) : (b[P] = ee, b[J] = L, P = J); + else if (ae < le && 0 > o(qe, L)) + b[P] = qe, b[ae] = L, P = ae; + else break e; + } + } + return N; + } + function o(b, N) { + var L = b.sortIndex - N.sortIndex; + return L !== 0 ? L : b.id - N.id; + } + if (l.unstable_now = void 0, typeof performance == "object" && typeof performance.now == "function") { + var p = performance; + l.unstable_now = function() { + return p.now(); + }; + } else { + var d = Date, m = d.now(); + l.unstable_now = function() { + return d.now() - m; + }; + } + var g = [], S = [], A = 1, w = null, x = 3, _ = !1, B = !1, Z = !1, oe = !1, C = typeof setTimeout == "function" ? setTimeout : null, ce = typeof clearTimeout == "function" ? clearTimeout : null, me = typeof setImmediate < "u" ? setImmediate : null; + function fe(b) { + for (var N = s(S); N !== null; ) { + if (N.callback === null) c(S); + else if (N.startTime <= b) + c(S), N.sortIndex = N.expirationTime, i(g, N); + else break; + N = s(S); + } + } + function Be(b) { + if (Z = !1, fe(b), !B) + if (s(g) !== null) + B = !0, K || (K = !0, H()); + else { + var N = s(S); + N !== null && be(Be, N.startTime - b); + } + } + var K = !1, I = -1, ne = 5, ue = -1; + function V() { + return oe ? !0 : !(l.unstable_now() - ue < ne); + } + function j() { + if (oe = !1, K) { + var b = l.unstable_now(); + ue = b; + var N = !0; + try { + e: { + B = !1, Z && (Z = !1, ce(I), I = -1), _ = !0; + var L = x; + try { + t: { + for (fe(b), w = s(g); w !== null && !(w.expirationTime > b && V()); ) { + var P = w.callback; + if (typeof P == "function") { + w.callback = null, x = w.priorityLevel; + var le = P( + w.expirationTime <= b + ); + if (b = l.unstable_now(), typeof le == "function") { + w.callback = le, fe(b), N = !0; + break t; + } + w === s(g) && c(g), fe(b); + } else c(g); + w = s(g); + } + if (w !== null) N = !0; + else { + var Ee = s(S); + Ee !== null && be( + Be, + Ee.startTime - b + ), N = !1; + } + } + break e; + } finally { + w = null, x = L, _ = !1; + } + N = void 0; + } + } finally { + N ? H() : K = !1; + } + } + } + var H; + if (typeof me == "function") + H = function() { + me(j); + }; + else if (typeof MessageChannel < "u") { + var Y = new MessageChannel(), $ = Y.port2; + Y.port1.onmessage = j, H = function() { + $.postMessage(null); + }; + } else + H = function() { + C(j, 0); + }; + function be(b, N) { + I = C(function() { + b(l.unstable_now()); + }, N); + } + l.unstable_IdlePriority = 5, l.unstable_ImmediatePriority = 1, l.unstable_LowPriority = 4, l.unstable_NormalPriority = 3, l.unstable_Profiling = null, l.unstable_UserBlockingPriority = 2, l.unstable_cancelCallback = function(b) { + b.callback = null; + }, l.unstable_forceFrameRate = function(b) { + 0 > b || 125 < b ? console.error( + "forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported" + ) : ne = 0 < b ? Math.floor(1e3 / b) : 5; + }, l.unstable_getCurrentPriorityLevel = function() { + return x; + }, l.unstable_next = function(b) { + switch (x) { + case 1: + case 2: + case 3: + var N = 3; + break; + default: + N = x; + } + var L = x; + x = N; + try { + return b(); + } finally { + x = L; + } + }, l.unstable_requestPaint = function() { + oe = !0; + }, l.unstable_runWithPriority = function(b, N) { + switch (b) { + case 1: + case 2: + case 3: + case 4: + case 5: + break; + default: + b = 3; + } + var L = x; + x = b; + try { + return N(); + } finally { + x = L; + } + }, l.unstable_scheduleCallback = function(b, N, L) { + var P = l.unstable_now(); + switch (typeof L == "object" && L !== null ? (L = L.delay, L = typeof L == "number" && 0 < L ? P + L : P) : L = P, b) { + case 1: + var le = -1; + break; + case 2: + le = 250; + break; + case 5: + le = 1073741823; + break; + case 4: + le = 1e4; + break; + default: + le = 5e3; + } + return le = L + le, b = { + id: A++, + callback: N, + priorityLevel: b, + startTime: L, + expirationTime: le, + sortIndex: -1 + }, L > P ? (b.sortIndex = L, i(S, b), s(g) === null && b === s(S) && (Z ? (ce(I), I = -1) : Z = !0, be(Be, L - P))) : (b.sortIndex = le, i(g, b), B || _ || (B = !0, K || (K = !0, H()))), b; + }, l.unstable_shouldYield = V, l.unstable_wrapCallback = function(b) { + var N = x; + return function() { + var L = x; + x = N; + try { + return b.apply(this, arguments); + } finally { + x = L; + } + }; + }; + })(df)), df; +} +var pp; +function g1() { + return pp || (pp = 1, of.exports = y1()), of.exports; +} +var yp; +function v1() { + if (yp) return Di; + yp = 1; + var l = g1(), i = window.React, s = window.ReactDOM; + function c(e) { + var t = "https://react.dev/errors/" + e; + if (1 < arguments.length) { + t += "?args[]=" + encodeURIComponent(arguments[1]); + for (var n = 2; n < arguments.length; n++) + t += "&args[]=" + encodeURIComponent(arguments[n]); + } + return "Minified React error #" + e + "; visit " + t + " for the full message or use the non-minified dev environment for full errors and additional helpful warnings."; + } + function o(e) { + return !(!e || e.nodeType !== 1 && e.nodeType !== 9 && e.nodeType !== 11); + } + function p(e) { + var t = e, n = e; + if (e.alternate) for (; t.return; ) t = t.return; + else { + e = t; + do + t = e, (t.flags & 4098) !== 0 && (n = t.return), e = t.return; + while (e); + } + return t.tag === 3 ? n : null; + } + function d(e) { + if (e.tag === 13) { + var t = e.memoizedState; + if (t === null && (e = e.alternate, e !== null && (t = e.memoizedState)), t !== null) return t.dehydrated; + } + return null; + } + function m(e) { + if (e.tag === 31) { + var t = e.memoizedState; + if (t === null && (e = e.alternate, e !== null && (t = e.memoizedState)), t !== null) return t.dehydrated; + } + return null; + } + function g(e) { + if (p(e) !== e) + throw Error(c(188)); + } + function S(e) { + var t = e.alternate; + if (!t) { + if (t = p(e), t === null) throw Error(c(188)); + return t !== e ? null : e; + } + for (var n = e, a = t; ; ) { + var r = n.return; + if (r === null) break; + var u = r.alternate; + if (u === null) { + if (a = r.return, a !== null) { + n = a; + continue; + } + break; + } + if (r.child === u.child) { + for (u = r.child; u; ) { + if (u === n) return g(r), e; + if (u === a) return g(r), t; + u = u.sibling; + } + throw Error(c(188)); + } + if (n.return !== a.return) n = r, a = u; + else { + for (var f = !1, h = r.child; h; ) { + if (h === n) { + f = !0, n = r, a = u; + break; + } + if (h === a) { + f = !0, a = r, n = u; + break; + } + h = h.sibling; + } + if (!f) { + for (h = u.child; h; ) { + if (h === n) { + f = !0, n = u, a = r; + break; + } + if (h === a) { + f = !0, a = u, n = r; + break; + } + h = h.sibling; + } + if (!f) throw Error(c(189)); + } + } + if (n.alternate !== a) throw Error(c(190)); + } + if (n.tag !== 3) throw Error(c(188)); + return n.stateNode.current === n ? e : t; + } + function A(e) { + var t = e.tag; + if (t === 5 || t === 26 || t === 27 || t === 6) return e; + for (e = e.child; e !== null; ) { + if (t = A(e), t !== null) return t; + e = e.sibling; + } + return null; + } + var w = Object.assign, x = /* @__PURE__ */ Symbol.for("react.element"), _ = /* @__PURE__ */ Symbol.for("react.transitional.element"), B = /* @__PURE__ */ Symbol.for("react.portal"), Z = /* @__PURE__ */ Symbol.for("react.fragment"), oe = /* @__PURE__ */ Symbol.for("react.strict_mode"), C = /* @__PURE__ */ Symbol.for("react.profiler"), ce = /* @__PURE__ */ Symbol.for("react.consumer"), me = /* @__PURE__ */ Symbol.for("react.context"), fe = /* @__PURE__ */ Symbol.for("react.forward_ref"), Be = /* @__PURE__ */ Symbol.for("react.suspense"), K = /* @__PURE__ */ Symbol.for("react.suspense_list"), I = /* @__PURE__ */ Symbol.for("react.memo"), ne = /* @__PURE__ */ Symbol.for("react.lazy"), ue = /* @__PURE__ */ Symbol.for("react.activity"), V = /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel"), j = Symbol.iterator; + function H(e) { + return e === null || typeof e != "object" ? null : (e = j && e[j] || e["@@iterator"], typeof e == "function" ? e : null); + } + var Y = /* @__PURE__ */ Symbol.for("react.client.reference"); + function $(e) { + if (e == null) return null; + if (typeof e == "function") + return e.$$typeof === Y ? null : e.displayName || e.name || null; + if (typeof e == "string") return e; + switch (e) { + case Z: + return "Fragment"; + case C: + return "Profiler"; + case oe: + return "StrictMode"; + case Be: + return "Suspense"; + case K: + return "SuspenseList"; + case ue: + return "Activity"; + } + if (typeof e == "object") + switch (e.$$typeof) { + case B: + return "Portal"; + case me: + return e.displayName || "Context"; + case ce: + return (e._context.displayName || "Context") + ".Consumer"; + case fe: + var t = e.render; + return e = e.displayName, e || (e = t.displayName || t.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e; + case I: + return t = e.displayName || null, t !== null ? t : $(e.type) || "Memo"; + case ne: + t = e._payload, e = e._init; + try { + return $(e(t)); + } catch { + } + } + return null; + } + var be = Array.isArray, b = i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, N = s.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, L = { + pending: !1, + data: null, + method: null, + action: null + }, P = [], le = -1; + function Ee(e) { + return { current: e }; + } + function J(e) { + 0 > le || (e.current = P[le], P[le] = null, le--); + } + function ee(e, t) { + le++, P[le] = e.current, e.current = t; + } + var ae = Ee(null), qe = Ee(null), Pe = Ee(null), ht = Ee(null); + function Xe(e, t) { + switch (ee(Pe, t), ee(qe, e), ee(ae, null), t.nodeType) { + case 9: + case 11: + e = (e = t.documentElement) && (e = e.namespaceURI) ? hm(e) : 0; + break; + default: + if (e = t.tagName, t = t.namespaceURI) + t = hm(t), e = mm(t, e); + else + switch (e) { + case "svg": + e = 1; + break; + case "math": + e = 2; + break; + default: + e = 0; + } + } + J(ae), ee(ae, e); + } + function Je() { + J(ae), J(qe), J(Pe); + } + function _e(e) { + e.memoizedState !== null && ee(ht, e); + var t = ae.current, n = mm(t, e.type); + t !== n && (ee(qe, e), ee(ae, n)); + } + function Le(e) { + qe.current === e && (J(ae), J(qe)), ht.current === e && (J(ht), Si._currentValue = L); + } + var rt, Ut; + function xe(e) { + if (rt === void 0) + try { + throw Error(); + } catch (n) { + var t = n.stack.trim().match(/\n( *(at )?)/); + rt = t && t[1] || "", Ut = -1 < n.stack.indexOf(` + at`) ? " ()" : -1 < n.stack.indexOf("@") ? "@unknown:0:0" : ""; + } + return ` +` + rt + e + Ut; + } + var te = !1; + function ie(e, t) { + if (!e || te) return ""; + te = !0; + var n = Error.prepareStackTrace; + Error.prepareStackTrace = void 0; + try { + var a = { + DetermineComponentFrameRoot: function() { + try { + if (t) { + var U = function() { + throw Error(); + }; + if (Object.defineProperty(U.prototype, "props", { + set: function() { + throw Error(); + } + }), typeof Reflect == "object" && Reflect.construct) { + try { + Reflect.construct(U, []); + } catch (D) { + var z = D; + } + Reflect.construct(e, [], U); + } else { + try { + U.call(); + } catch (D) { + z = D; + } + e.call(U.prototype); + } + } else { + try { + throw Error(); + } catch (D) { + z = D; + } + (U = e()) && typeof U.catch == "function" && U.catch(function() { + }); + } + } catch (D) { + if (D && z && typeof D.stack == "string") + return [D.stack, z.stack]; + } + return [null, null]; + } + }; + a.DetermineComponentFrameRoot.displayName = "DetermineComponentFrameRoot"; + var r = Object.getOwnPropertyDescriptor( + a.DetermineComponentFrameRoot, + "name" + ); + r && r.configurable && Object.defineProperty( + a.DetermineComponentFrameRoot, + "name", + { value: "DetermineComponentFrameRoot" } + ); + var u = a.DetermineComponentFrameRoot(), f = u[0], h = u[1]; + if (f && h) { + var y = f.split(` +`), R = h.split(` +`); + for (r = a = 0; a < y.length && !y[a].includes("DetermineComponentFrameRoot"); ) + a++; + for (; r < R.length && !R[r].includes( + "DetermineComponentFrameRoot" + ); ) + r++; + if (a === y.length || r === R.length) + for (a = y.length - 1, r = R.length - 1; 1 <= a && 0 <= r && y[a] !== R[r]; ) + r--; + for (; 1 <= a && 0 <= r; a--, r--) + if (y[a] !== R[r]) { + if (a !== 1 || r !== 1) + do + if (a--, r--, 0 > r || y[a] !== R[r]) { + var M = ` +` + y[a].replace(" at new ", " at "); + return e.displayName && M.includes("") && (M = M.replace("", e.displayName)), M; + } + while (1 <= a && 0 <= r); + break; + } + } + } finally { + te = !1, Error.prepareStackTrace = n; + } + return (n = e ? e.displayName || e.name : "") ? xe(n) : ""; + } + function Q(e, t) { + switch (e.tag) { + case 26: + case 27: + case 5: + return xe(e.type); + case 16: + return xe("Lazy"); + case 13: + return e.child !== t && t !== null ? xe("Suspense Fallback") : xe("Suspense"); + case 19: + return xe("SuspenseList"); + case 0: + case 15: + return ie(e.type, !1); + case 11: + return ie(e.type.render, !1); + case 1: + return ie(e.type, !0); + case 31: + return xe("Activity"); + default: + return ""; + } + } + function ge(e) { + try { + var t = "", n = null; + do + t += Q(e, n), n = e, e = e.return; + while (e); + return t; + } catch (a) { + return ` +Error generating stack: ` + a.message + ` +` + a.stack; + } + } + var W = Object.prototype.hasOwnProperty, we = l.unstable_scheduleCallback, nt = l.unstable_cancelCallback, St = l.unstable_shouldYield, Hl = l.unstable_requestPaint, st = l.unstable_now, ml = l.unstable_getCurrentPriorityLevel, Fe = l.unstable_ImmediatePriority, gn = l.unstable_UserBlockingPriority, Bl = l.unstable_NormalPriority, Ua = l.unstable_LowPriority, Kf = l.unstable_IdlePriority, Rg = l.log, zg = l.unstable_setDisableYieldValue, xa = null, xt = null; + function Cn(e) { + if (typeof Rg == "function" && zg(e), xt && typeof xt.setStrictMode == "function") + try { + xt.setStrictMode(xa, e); + } catch { + } + } + var Nt = Math.clz32 ? Math.clz32 : Tg, Dg = Math.log, Mg = Math.LN2; + function Tg(e) { + return e >>>= 0, e === 0 ? 32 : 31 - (Dg(e) / Mg | 0) | 0; + } + var Zi = 256, Vi = 262144, Ki = 4194304; + function pl(e) { + var t = e & 42; + if (t !== 0) return t; + switch (e & -e) { + case 1: + return 1; + case 2: + return 2; + case 4: + return 4; + case 8: + return 8; + case 16: + return 16; + case 32: + return 32; + case 64: + return 64; + case 128: + return 128; + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + case 32768: + case 65536: + case 131072: + return e & 261888; + case 262144: + case 524288: + case 1048576: + case 2097152: + return e & 3932160; + case 4194304: + case 8388608: + case 16777216: + case 33554432: + return e & 62914560; + case 67108864: + return 67108864; + case 134217728: + return 134217728; + case 268435456: + return 268435456; + case 536870912: + return 536870912; + case 1073741824: + return 0; + default: + return e; + } + } + function Pi(e, t, n) { + var a = e.pendingLanes; + if (a === 0) return 0; + var r = 0, u = e.suspendedLanes, f = e.pingedLanes; + e = e.warmLanes; + var h = a & 134217727; + return h !== 0 ? (a = h & ~u, a !== 0 ? r = pl(a) : (f &= h, f !== 0 ? r = pl(f) : n || (n = h & ~e, n !== 0 && (r = pl(n))))) : (h = a & ~u, h !== 0 ? r = pl(h) : f !== 0 ? r = pl(f) : n || (n = a & ~e, n !== 0 && (r = pl(n)))), r === 0 ? 0 : t !== 0 && t !== r && (t & u) === 0 && (u = r & -r, n = t & -t, u >= n || u === 32 && (n & 4194048) !== 0) ? t : r; + } + function Na(e, t) { + return (e.pendingLanes & ~(e.suspendedLanes & ~e.pingedLanes) & t) === 0; + } + function Ug(e, t) { + switch (e) { + case 1: + case 2: + case 4: + case 8: + case 64: + return t + 250; + case 16: + case 32: + case 128: + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + case 32768: + case 65536: + case 131072: + case 262144: + case 524288: + case 1048576: + case 2097152: + return t + 5e3; + case 4194304: + case 8388608: + case 16777216: + case 33554432: + return -1; + case 67108864: + case 134217728: + case 268435456: + case 536870912: + case 1073741824: + return -1; + default: + return -1; + } + } + function Pf() { + var e = Ki; + return Ki <<= 1, (Ki & 62914560) === 0 && (Ki = 4194304), e; + } + function Zs(e) { + for (var t = [], n = 0; 31 > n; n++) t.push(e); + return t; + } + function qa(e, t) { + e.pendingLanes |= t, t !== 268435456 && (e.suspendedLanes = 0, e.pingedLanes = 0, e.warmLanes = 0); + } + function xg(e, t, n, a, r, u) { + var f = e.pendingLanes; + e.pendingLanes = n, e.suspendedLanes = 0, e.pingedLanes = 0, e.warmLanes = 0, e.expiredLanes &= n, e.entangledLanes &= n, e.errorRecoveryDisabledLanes &= n, e.shellSuspendCounter = 0; + var h = e.entanglements, y = e.expirationTimes, R = e.hiddenUpdates; + for (n = f & ~n; 0 < n; ) { + var M = 31 - Nt(n), U = 1 << M; + h[M] = 0, y[M] = -1; + var z = R[M]; + if (z !== null) + for (R[M] = null, M = 0; M < z.length; M++) { + var D = z[M]; + D !== null && (D.lane &= -536870913); + } + n &= ~U; + } + a !== 0 && Jf(e, a, 0), u !== 0 && r === 0 && e.tag !== 0 && (e.suspendedLanes |= u & ~(f & ~t)); + } + function Jf(e, t, n) { + e.pendingLanes |= t, e.suspendedLanes &= ~t; + var a = 31 - Nt(t); + e.entangledLanes |= t, e.entanglements[a] = e.entanglements[a] | 1073741824 | n & 261930; + } + function Ff(e, t) { + var n = e.entangledLanes |= t; + for (e = e.entanglements; n; ) { + var a = 31 - Nt(n), r = 1 << a; + r & t | e[a] & t && (e[a] |= t), n &= ~r; + } + } + function $f(e, t) { + var n = t & -t; + return n = (n & 42) !== 0 ? 1 : Vs(n), (n & (e.suspendedLanes | t)) !== 0 ? 0 : n; + } + function Vs(e) { + switch (e) { + case 2: + e = 1; + break; + case 8: + e = 4; + break; + case 32: + e = 16; + break; + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + case 32768: + case 65536: + case 131072: + case 262144: + case 524288: + case 1048576: + case 2097152: + case 4194304: + case 8388608: + case 16777216: + case 33554432: + e = 128; + break; + case 268435456: + e = 134217728; + break; + default: + e = 0; + } + return e; + } + function Ks(e) { + return e &= -e, 2 < e ? 8 < e ? (e & 134217727) !== 0 ? 32 : 268435456 : 8 : 2; + } + function Wf() { + var e = N.p; + return e !== 0 ? e : (e = window.event, e === void 0 ? 32 : _m(e.type)); + } + function kf(e, t) { + var n = N.p; + try { + return N.p = e, t(); + } finally { + N.p = n; + } + } + var Ln = Math.random().toString(36).slice(2), ut = "__reactFiber$" + Ln, bt = "__reactProps$" + Ln, _l = "__reactContainer$" + Ln, Ps = "__reactEvents$" + Ln, Ng = "__reactListeners$" + Ln, qg = "__reactHandles$" + Ln, If = "__reactResources$" + Ln, ja = "__reactMarker$" + Ln; + function Js(e) { + delete e[ut], delete e[bt], delete e[Ps], delete e[Ng], delete e[qg]; + } + function Cl(e) { + var t = e[ut]; + if (t) return t; + for (var n = e.parentNode; n; ) { + if (t = n[_l] || n[ut]) { + if (n = t.alternate, t.child !== null || n !== null && n.child !== null) + for (e = Em(e); e !== null; ) { + if (n = e[ut]) return n; + e = Em(e); + } + return t; + } + e = n, n = e.parentNode; + } + return null; + } + function Ll(e) { + if (e = e[ut] || e[_l]) { + var t = e.tag; + if (t === 5 || t === 6 || t === 13 || t === 31 || t === 26 || t === 27 || t === 3) + return e; + } + return null; + } + function Ha(e) { + var t = e.tag; + if (t === 5 || t === 26 || t === 27 || t === 6) return e.stateNode; + throw Error(c(33)); + } + function Xl(e) { + var t = e[If]; + return t || (t = e[If] = { hoistableStyles: /* @__PURE__ */ new Map(), hoistableScripts: /* @__PURE__ */ new Map() }), t; + } + function at(e) { + e[ja] = !0; + } + var eo = /* @__PURE__ */ new Set(), to = {}; + function yl(e, t) { + Yl(e, t), Yl(e + "Capture", t); + } + function Yl(e, t) { + for (to[e] = t, e = 0; e < t.length; e++) + eo.add(t[e]); + } + var jg = RegExp( + "^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$" + ), no = {}, lo = {}; + function Hg(e) { + return W.call(lo, e) ? !0 : W.call(no, e) ? !1 : jg.test(e) ? lo[e] = !0 : (no[e] = !0, !1); + } + function Ji(e, t, n) { + if (Hg(t)) + if (n === null) e.removeAttribute(t); + else { + switch (typeof n) { + case "undefined": + case "function": + case "symbol": + e.removeAttribute(t); + return; + case "boolean": + var a = t.toLowerCase().slice(0, 5); + if (a !== "data-" && a !== "aria-") { + e.removeAttribute(t); + return; + } + } + e.setAttribute(t, "" + n); + } + } + function Fi(e, t, n) { + if (n === null) e.removeAttribute(t); + else { + switch (typeof n) { + case "undefined": + case "function": + case "symbol": + case "boolean": + e.removeAttribute(t); + return; + } + e.setAttribute(t, "" + n); + } + } + function vn(e, t, n, a) { + if (a === null) e.removeAttribute(n); + else { + switch (typeof a) { + case "undefined": + case "function": + case "symbol": + case "boolean": + e.removeAttribute(n); + return; + } + e.setAttributeNS(t, n, "" + a); + } + } + function Gt(e) { + switch (typeof e) { + case "bigint": + case "boolean": + case "number": + case "string": + case "undefined": + return e; + case "object": + return e; + default: + return ""; + } + } + function ao(e) { + var t = e.type; + return (e = e.nodeName) && e.toLowerCase() === "input" && (t === "checkbox" || t === "radio"); + } + function Bg(e, t, n) { + var a = Object.getOwnPropertyDescriptor( + e.constructor.prototype, + t + ); + if (!e.hasOwnProperty(t) && typeof a < "u" && typeof a.get == "function" && typeof a.set == "function") { + var r = a.get, u = a.set; + return Object.defineProperty(e, t, { + configurable: !0, + get: function() { + return r.call(this); + }, + set: function(f) { + n = "" + f, u.call(this, f); + } + }), Object.defineProperty(e, t, { + enumerable: a.enumerable + }), { + getValue: function() { + return n; + }, + setValue: function(f) { + n = "" + f; + }, + stopTracking: function() { + e._valueTracker = null, delete e[t]; + } + }; + } + } + function Fs(e) { + if (!e._valueTracker) { + var t = ao(e) ? "checked" : "value"; + e._valueTracker = Bg( + e, + t, + "" + e[t] + ); + } + } + function io(e) { + if (!e) return !1; + var t = e._valueTracker; + if (!t) return !0; + var n = t.getValue(), a = ""; + return e && (a = ao(e) ? e.checked ? "true" : "false" : e.value), e = a, e !== n ? (t.setValue(e), !0) : !1; + } + function $i(e) { + if (e = e || (typeof document < "u" ? document : void 0), typeof e > "u") return null; + try { + return e.activeElement || e.body; + } catch { + return e.body; + } + } + var _g = /[\n"\\]/g; + function Zt(e) { + return e.replace( + _g, + function(t) { + return "\\" + t.charCodeAt(0).toString(16) + " "; + } + ); + } + function $s(e, t, n, a, r, u, f, h) { + e.name = "", f != null && typeof f != "function" && typeof f != "symbol" && typeof f != "boolean" ? e.type = f : e.removeAttribute("type"), t != null ? f === "number" ? (t === 0 && e.value === "" || e.value != t) && (e.value = "" + Gt(t)) : e.value !== "" + Gt(t) && (e.value = "" + Gt(t)) : f !== "submit" && f !== "reset" || e.removeAttribute("value"), t != null ? Ws(e, f, Gt(t)) : n != null ? Ws(e, f, Gt(n)) : a != null && e.removeAttribute("value"), r == null && u != null && (e.defaultChecked = !!u), r != null && (e.checked = r && typeof r != "function" && typeof r != "symbol"), h != null && typeof h != "function" && typeof h != "symbol" && typeof h != "boolean" ? e.name = "" + Gt(h) : e.removeAttribute("name"); + } + function ro(e, t, n, a, r, u, f, h) { + if (u != null && typeof u != "function" && typeof u != "symbol" && typeof u != "boolean" && (e.type = u), t != null || n != null) { + if (!(u !== "submit" && u !== "reset" || t != null)) { + Fs(e); + return; + } + n = n != null ? "" + Gt(n) : "", t = t != null ? "" + Gt(t) : n, h || t === e.value || (e.value = t), e.defaultValue = t; + } + a = a ?? r, a = typeof a != "function" && typeof a != "symbol" && !!a, e.checked = h ? e.checked : !!a, e.defaultChecked = !!a, f != null && typeof f != "function" && typeof f != "symbol" && typeof f != "boolean" && (e.name = f), Fs(e); + } + function Ws(e, t, n) { + t === "number" && $i(e.ownerDocument) === e || e.defaultValue === "" + n || (e.defaultValue = "" + n); + } + function Ql(e, t, n, a) { + if (e = e.options, t) { + t = {}; + for (var r = 0; r < n.length; r++) + t["$" + n[r]] = !0; + for (n = 0; n < e.length; n++) + r = t.hasOwnProperty("$" + e[n].value), e[n].selected !== r && (e[n].selected = r), r && a && (e[n].defaultSelected = !0); + } else { + for (n = "" + Gt(n), t = null, r = 0; r < e.length; r++) { + if (e[r].value === n) { + e[r].selected = !0, a && (e[r].defaultSelected = !0); + return; + } + t !== null || e[r].disabled || (t = e[r]); + } + t !== null && (t.selected = !0); + } + } + function so(e, t, n) { + if (t != null && (t = "" + Gt(t), t !== e.value && (e.value = t), n == null)) { + e.defaultValue !== t && (e.defaultValue = t); + return; + } + e.defaultValue = n != null ? "" + Gt(n) : ""; + } + function uo(e, t, n, a) { + if (t == null) { + if (a != null) { + if (n != null) throw Error(c(92)); + if (be(a)) { + if (1 < a.length) throw Error(c(93)); + a = a[0]; + } + n = a; + } + n == null && (n = ""), t = n; + } + n = Gt(t), e.defaultValue = n, a = e.textContent, a === n && a !== "" && a !== null && (e.value = a), Fs(e); + } + function Gl(e, t) { + if (t) { + var n = e.firstChild; + if (n && n === e.lastChild && n.nodeType === 3) { + n.nodeValue = t; + return; + } + } + e.textContent = t; + } + var Cg = new Set( + "animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split( + " " + ) + ); + function co(e, t, n) { + var a = t.indexOf("--") === 0; + n == null || typeof n == "boolean" || n === "" ? a ? e.setProperty(t, "") : t === "float" ? e.cssFloat = "" : e[t] = "" : a ? e.setProperty(t, n) : typeof n != "number" || n === 0 || Cg.has(t) ? t === "float" ? e.cssFloat = n : e[t] = ("" + n).trim() : e[t] = n + "px"; + } + function fo(e, t, n) { + if (t != null && typeof t != "object") + throw Error(c(62)); + if (e = e.style, n != null) { + for (var a in n) + !n.hasOwnProperty(a) || t != null && t.hasOwnProperty(a) || (a.indexOf("--") === 0 ? e.setProperty(a, "") : a === "float" ? e.cssFloat = "" : e[a] = ""); + for (var r in t) + a = t[r], t.hasOwnProperty(r) && n[r] !== a && co(e, r, a); + } else + for (var u in t) + t.hasOwnProperty(u) && co(e, u, t[u]); + } + function ks(e) { + if (e.indexOf("-") === -1) return !1; + switch (e) { + case "annotation-xml": + case "color-profile": + case "font-face": + case "font-face-src": + case "font-face-uri": + case "font-face-format": + case "font-face-name": + case "missing-glyph": + return !1; + default: + return !0; + } + } + var Lg = /* @__PURE__ */ new Map([ + ["acceptCharset", "accept-charset"], + ["htmlFor", "for"], + ["httpEquiv", "http-equiv"], + ["crossOrigin", "crossorigin"], + ["accentHeight", "accent-height"], + ["alignmentBaseline", "alignment-baseline"], + ["arabicForm", "arabic-form"], + ["baselineShift", "baseline-shift"], + ["capHeight", "cap-height"], + ["clipPath", "clip-path"], + ["clipRule", "clip-rule"], + ["colorInterpolation", "color-interpolation"], + ["colorInterpolationFilters", "color-interpolation-filters"], + ["colorProfile", "color-profile"], + ["colorRendering", "color-rendering"], + ["dominantBaseline", "dominant-baseline"], + ["enableBackground", "enable-background"], + ["fillOpacity", "fill-opacity"], + ["fillRule", "fill-rule"], + ["floodColor", "flood-color"], + ["floodOpacity", "flood-opacity"], + ["fontFamily", "font-family"], + ["fontSize", "font-size"], + ["fontSizeAdjust", "font-size-adjust"], + ["fontStretch", "font-stretch"], + ["fontStyle", "font-style"], + ["fontVariant", "font-variant"], + ["fontWeight", "font-weight"], + ["glyphName", "glyph-name"], + ["glyphOrientationHorizontal", "glyph-orientation-horizontal"], + ["glyphOrientationVertical", "glyph-orientation-vertical"], + ["horizAdvX", "horiz-adv-x"], + ["horizOriginX", "horiz-origin-x"], + ["imageRendering", "image-rendering"], + ["letterSpacing", "letter-spacing"], + ["lightingColor", "lighting-color"], + ["markerEnd", "marker-end"], + ["markerMid", "marker-mid"], + ["markerStart", "marker-start"], + ["overlinePosition", "overline-position"], + ["overlineThickness", "overline-thickness"], + ["paintOrder", "paint-order"], + ["panose-1", "panose-1"], + ["pointerEvents", "pointer-events"], + ["renderingIntent", "rendering-intent"], + ["shapeRendering", "shape-rendering"], + ["stopColor", "stop-color"], + ["stopOpacity", "stop-opacity"], + ["strikethroughPosition", "strikethrough-position"], + ["strikethroughThickness", "strikethrough-thickness"], + ["strokeDasharray", "stroke-dasharray"], + ["strokeDashoffset", "stroke-dashoffset"], + ["strokeLinecap", "stroke-linecap"], + ["strokeLinejoin", "stroke-linejoin"], + ["strokeMiterlimit", "stroke-miterlimit"], + ["strokeOpacity", "stroke-opacity"], + ["strokeWidth", "stroke-width"], + ["textAnchor", "text-anchor"], + ["textDecoration", "text-decoration"], + ["textRendering", "text-rendering"], + ["transformOrigin", "transform-origin"], + ["underlinePosition", "underline-position"], + ["underlineThickness", "underline-thickness"], + ["unicodeBidi", "unicode-bidi"], + ["unicodeRange", "unicode-range"], + ["unitsPerEm", "units-per-em"], + ["vAlphabetic", "v-alphabetic"], + ["vHanging", "v-hanging"], + ["vIdeographic", "v-ideographic"], + ["vMathematical", "v-mathematical"], + ["vectorEffect", "vector-effect"], + ["vertAdvY", "vert-adv-y"], + ["vertOriginX", "vert-origin-x"], + ["vertOriginY", "vert-origin-y"], + ["wordSpacing", "word-spacing"], + ["writingMode", "writing-mode"], + ["xmlnsXlink", "xmlns:xlink"], + ["xHeight", "x-height"] + ]), Xg = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i; + function Wi(e) { + return Xg.test("" + e) ? "javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')" : e; + } + function Sn() { + } + var Is = null; + function eu(e) { + return e = e.target || e.srcElement || window, e.correspondingUseElement && (e = e.correspondingUseElement), e.nodeType === 3 ? e.parentNode : e; + } + var Zl = null, Vl = null; + function oo(e) { + var t = Ll(e); + if (t && (e = t.stateNode)) { + var n = e[bt] || null; + e: switch (e = t.stateNode, t.type) { + case "input": + if ($s( + e, + n.value, + n.defaultValue, + n.defaultValue, + n.checked, + n.defaultChecked, + n.type, + n.name + ), t = n.name, n.type === "radio" && t != null) { + for (n = e; n.parentNode; ) n = n.parentNode; + for (n = n.querySelectorAll( + 'input[name="' + Zt( + "" + t + ) + '"][type="radio"]' + ), t = 0; t < n.length; t++) { + var a = n[t]; + if (a !== e && a.form === e.form) { + var r = a[bt] || null; + if (!r) throw Error(c(90)); + $s( + a, + r.value, + r.defaultValue, + r.defaultValue, + r.checked, + r.defaultChecked, + r.type, + r.name + ); + } + } + for (t = 0; t < n.length; t++) + a = n[t], a.form === e.form && io(a); + } + break e; + case "textarea": + so(e, n.value, n.defaultValue); + break e; + case "select": + t = n.value, t != null && Ql(e, !!n.multiple, t, !1); + } + } + } + var tu = !1; + function ho(e, t, n) { + if (tu) return e(t, n); + tu = !0; + try { + var a = e(t); + return a; + } finally { + if (tu = !1, (Zl !== null || Vl !== null) && (Cr(), Zl && (t = Zl, e = Vl, Vl = Zl = null, oo(t), e))) + for (t = 0; t < e.length; t++) oo(e[t]); + } + } + function Ba(e, t) { + var n = e.stateNode; + if (n === null) return null; + var a = n[bt] || null; + if (a === null) return null; + n = a[t]; + e: switch (t) { + case "onClick": + case "onClickCapture": + case "onDoubleClick": + case "onDoubleClickCapture": + case "onMouseDown": + case "onMouseDownCapture": + case "onMouseMove": + case "onMouseMoveCapture": + case "onMouseUp": + case "onMouseUpCapture": + case "onMouseEnter": + (a = !a.disabled) || (e = e.type, a = !(e === "button" || e === "input" || e === "select" || e === "textarea")), e = !a; + break e; + default: + e = !1; + } + if (e) return null; + if (n && typeof n != "function") + throw Error( + c(231, t, typeof n) + ); + return n; + } + var bn = !(typeof window > "u" || typeof window.document > "u" || typeof window.document.createElement > "u"), nu = !1; + if (bn) + try { + var _a = {}; + Object.defineProperty(_a, "passive", { + get: function() { + nu = !0; + } + }), window.addEventListener("test", _a, _a), window.removeEventListener("test", _a, _a); + } catch { + nu = !1; + } + var Xn = null, lu = null, ki = null; + function mo() { + if (ki) return ki; + var e, t = lu, n = t.length, a, r = "value" in Xn ? Xn.value : Xn.textContent, u = r.length; + for (e = 0; e < n && t[e] === r[e]; e++) ; + var f = n - e; + for (a = 1; a <= f && t[n - a] === r[u - a]; a++) ; + return ki = r.slice(e, 1 < a ? 1 - a : void 0); + } + function Ii(e) { + var t = e.keyCode; + return "charCode" in e ? (e = e.charCode, e === 0 && t === 13 && (e = 13)) : e = t, e === 10 && (e = 13), 32 <= e || e === 13 ? e : 0; + } + function er() { + return !0; + } + function po() { + return !1; + } + function Et(e) { + function t(n, a, r, u, f) { + this._reactName = n, this._targetInst = r, this.type = a, this.nativeEvent = u, this.target = f, this.currentTarget = null; + for (var h in e) + e.hasOwnProperty(h) && (n = e[h], this[h] = n ? n(u) : u[h]); + return this.isDefaultPrevented = (u.defaultPrevented != null ? u.defaultPrevented : u.returnValue === !1) ? er : po, this.isPropagationStopped = po, this; + } + return w(t.prototype, { + preventDefault: function() { + this.defaultPrevented = !0; + var n = this.nativeEvent; + n && (n.preventDefault ? n.preventDefault() : typeof n.returnValue != "unknown" && (n.returnValue = !1), this.isDefaultPrevented = er); + }, + stopPropagation: function() { + var n = this.nativeEvent; + n && (n.stopPropagation ? n.stopPropagation() : typeof n.cancelBubble != "unknown" && (n.cancelBubble = !0), this.isPropagationStopped = er); + }, + persist: function() { + }, + isPersistent: er + }), t; + } + var gl = { + eventPhase: 0, + bubbles: 0, + cancelable: 0, + timeStamp: function(e) { + return e.timeStamp || Date.now(); + }, + defaultPrevented: 0, + isTrusted: 0 + }, tr = Et(gl), Ca = w({}, gl, { view: 0, detail: 0 }), Yg = Et(Ca), au, iu, La, nr = w({}, Ca, { + screenX: 0, + screenY: 0, + clientX: 0, + clientY: 0, + pageX: 0, + pageY: 0, + ctrlKey: 0, + shiftKey: 0, + altKey: 0, + metaKey: 0, + getModifierState: su, + button: 0, + buttons: 0, + relatedTarget: function(e) { + return e.relatedTarget === void 0 ? e.fromElement === e.srcElement ? e.toElement : e.fromElement : e.relatedTarget; + }, + movementX: function(e) { + return "movementX" in e ? e.movementX : (e !== La && (La && e.type === "mousemove" ? (au = e.screenX - La.screenX, iu = e.screenY - La.screenY) : iu = au = 0, La = e), au); + }, + movementY: function(e) { + return "movementY" in e ? e.movementY : iu; + } + }), yo = Et(nr), Qg = w({}, nr, { dataTransfer: 0 }), Gg = Et(Qg), Zg = w({}, Ca, { relatedTarget: 0 }), ru = Et(Zg), Vg = w({}, gl, { + animationName: 0, + elapsedTime: 0, + pseudoElement: 0 + }), Kg = Et(Vg), Pg = w({}, gl, { + clipboardData: function(e) { + return "clipboardData" in e ? e.clipboardData : window.clipboardData; + } + }), Jg = Et(Pg), Fg = w({}, gl, { data: 0 }), go = Et(Fg), $g = { + Esc: "Escape", + Spacebar: " ", + Left: "ArrowLeft", + Up: "ArrowUp", + Right: "ArrowRight", + Down: "ArrowDown", + Del: "Delete", + Win: "OS", + Menu: "ContextMenu", + Apps: "ContextMenu", + Scroll: "ScrollLock", + MozPrintableKey: "Unidentified" + }, Wg = { + 8: "Backspace", + 9: "Tab", + 12: "Clear", + 13: "Enter", + 16: "Shift", + 17: "Control", + 18: "Alt", + 19: "Pause", + 20: "CapsLock", + 27: "Escape", + 32: " ", + 33: "PageUp", + 34: "PageDown", + 35: "End", + 36: "Home", + 37: "ArrowLeft", + 38: "ArrowUp", + 39: "ArrowRight", + 40: "ArrowDown", + 45: "Insert", + 46: "Delete", + 112: "F1", + 113: "F2", + 114: "F3", + 115: "F4", + 116: "F5", + 117: "F6", + 118: "F7", + 119: "F8", + 120: "F9", + 121: "F10", + 122: "F11", + 123: "F12", + 144: "NumLock", + 145: "ScrollLock", + 224: "Meta" + }, kg = { + Alt: "altKey", + Control: "ctrlKey", + Meta: "metaKey", + Shift: "shiftKey" + }; + function Ig(e) { + var t = this.nativeEvent; + return t.getModifierState ? t.getModifierState(e) : (e = kg[e]) ? !!t[e] : !1; + } + function su() { + return Ig; + } + var e0 = w({}, Ca, { + key: function(e) { + if (e.key) { + var t = $g[e.key] || e.key; + if (t !== "Unidentified") return t; + } + return e.type === "keypress" ? (e = Ii(e), e === 13 ? "Enter" : String.fromCharCode(e)) : e.type === "keydown" || e.type === "keyup" ? Wg[e.keyCode] || "Unidentified" : ""; + }, + code: 0, + location: 0, + ctrlKey: 0, + shiftKey: 0, + altKey: 0, + metaKey: 0, + repeat: 0, + locale: 0, + getModifierState: su, + charCode: function(e) { + return e.type === "keypress" ? Ii(e) : 0; + }, + keyCode: function(e) { + return e.type === "keydown" || e.type === "keyup" ? e.keyCode : 0; + }, + which: function(e) { + return e.type === "keypress" ? Ii(e) : e.type === "keydown" || e.type === "keyup" ? e.keyCode : 0; + } + }), t0 = Et(e0), n0 = w({}, nr, { + pointerId: 0, + width: 0, + height: 0, + pressure: 0, + tangentialPressure: 0, + tiltX: 0, + tiltY: 0, + twist: 0, + pointerType: 0, + isPrimary: 0 + }), vo = Et(n0), l0 = w({}, Ca, { + touches: 0, + targetTouches: 0, + changedTouches: 0, + altKey: 0, + metaKey: 0, + ctrlKey: 0, + shiftKey: 0, + getModifierState: su + }), a0 = Et(l0), i0 = w({}, gl, { + propertyName: 0, + elapsedTime: 0, + pseudoElement: 0 + }), r0 = Et(i0), s0 = w({}, nr, { + deltaX: function(e) { + return "deltaX" in e ? e.deltaX : "wheelDeltaX" in e ? -e.wheelDeltaX : 0; + }, + deltaY: function(e) { + return "deltaY" in e ? e.deltaY : "wheelDeltaY" in e ? -e.wheelDeltaY : "wheelDelta" in e ? -e.wheelDelta : 0; + }, + deltaZ: 0, + deltaMode: 0 + }), u0 = Et(s0), c0 = w({}, gl, { + newState: 0, + oldState: 0 + }), f0 = Et(c0), o0 = [9, 13, 27, 32], uu = bn && "CompositionEvent" in window, Xa = null; + bn && "documentMode" in document && (Xa = document.documentMode); + var d0 = bn && "TextEvent" in window && !Xa, So = bn && (!uu || Xa && 8 < Xa && 11 >= Xa), bo = " ", Eo = !1; + function wo(e, t) { + switch (e) { + case "keyup": + return o0.indexOf(t.keyCode) !== -1; + case "keydown": + return t.keyCode !== 229; + case "keypress": + case "mousedown": + case "focusout": + return !0; + default: + return !1; + } + } + function Ao(e) { + return e = e.detail, typeof e == "object" && "data" in e ? e.data : null; + } + var Kl = !1; + function h0(e, t) { + switch (e) { + case "compositionend": + return Ao(t); + case "keypress": + return t.which !== 32 ? null : (Eo = !0, bo); + case "textInput": + return e = t.data, e === bo && Eo ? null : e; + default: + return null; + } + } + function m0(e, t) { + if (Kl) + return e === "compositionend" || !uu && wo(e, t) ? (e = mo(), ki = lu = Xn = null, Kl = !1, e) : null; + switch (e) { + case "paste": + return null; + case "keypress": + if (!(t.ctrlKey || t.altKey || t.metaKey) || t.ctrlKey && t.altKey) { + if (t.char && 1 < t.char.length) + return t.char; + if (t.which) return String.fromCharCode(t.which); + } + return null; + case "compositionend": + return So && t.locale !== "ko" ? null : t.data; + default: + return null; + } + } + var p0 = { + color: !0, + date: !0, + datetime: !0, + "datetime-local": !0, + email: !0, + month: !0, + number: !0, + password: !0, + range: !0, + search: !0, + tel: !0, + text: !0, + time: !0, + url: !0, + week: !0 + }; + function Oo(e) { + var t = e && e.nodeName && e.nodeName.toLowerCase(); + return t === "input" ? !!p0[e.type] : t === "textarea"; + } + function Ro(e, t, n, a) { + Zl ? Vl ? Vl.push(a) : Vl = [a] : Zl = a, t = Vr(t, "onChange"), 0 < t.length && (n = new tr( + "onChange", + "change", + null, + n, + a + ), e.push({ event: n, listeners: t })); + } + var Ya = null, Qa = null; + function y0(e) { + sm(e, 0); + } + function lr(e) { + var t = Ha(e); + if (io(t)) return e; + } + function zo(e, t) { + if (e === "change") return t; + } + var Do = !1; + if (bn) { + var cu; + if (bn) { + var fu = "oninput" in document; + if (!fu) { + var Mo = document.createElement("div"); + Mo.setAttribute("oninput", "return;"), fu = typeof Mo.oninput == "function"; + } + cu = fu; + } else cu = !1; + Do = cu && (!document.documentMode || 9 < document.documentMode); + } + function To() { + Ya && (Ya.detachEvent("onpropertychange", Uo), Qa = Ya = null); + } + function Uo(e) { + if (e.propertyName === "value" && lr(Qa)) { + var t = []; + Ro( + t, + Qa, + e, + eu(e) + ), ho(y0, t); + } + } + function g0(e, t, n) { + e === "focusin" ? (To(), Ya = t, Qa = n, Ya.attachEvent("onpropertychange", Uo)) : e === "focusout" && To(); + } + function v0(e) { + if (e === "selectionchange" || e === "keyup" || e === "keydown") + return lr(Qa); + } + function S0(e, t) { + if (e === "click") return lr(t); + } + function b0(e, t) { + if (e === "input" || e === "change") + return lr(t); + } + function E0(e, t) { + return e === t && (e !== 0 || 1 / e === 1 / t) || e !== e && t !== t; + } + var qt = typeof Object.is == "function" ? Object.is : E0; + function Ga(e, t) { + if (qt(e, t)) return !0; + if (typeof e != "object" || e === null || typeof t != "object" || t === null) + return !1; + var n = Object.keys(e), a = Object.keys(t); + if (n.length !== a.length) return !1; + for (a = 0; a < n.length; a++) { + var r = n[a]; + if (!W.call(t, r) || !qt(e[r], t[r])) + return !1; + } + return !0; + } + function xo(e) { + for (; e && e.firstChild; ) e = e.firstChild; + return e; + } + function No(e, t) { + var n = xo(e); + e = 0; + for (var a; n; ) { + if (n.nodeType === 3) { + if (a = e + n.textContent.length, e <= t && a >= t) + return { node: n, offset: t - e }; + e = a; + } + e: { + for (; n; ) { + if (n.nextSibling) { + n = n.nextSibling; + break e; + } + n = n.parentNode; + } + n = void 0; + } + n = xo(n); + } + } + function qo(e, t) { + return e && t ? e === t ? !0 : e && e.nodeType === 3 ? !1 : t && t.nodeType === 3 ? qo(e, t.parentNode) : "contains" in e ? e.contains(t) : e.compareDocumentPosition ? !!(e.compareDocumentPosition(t) & 16) : !1 : !1; + } + function jo(e) { + e = e != null && e.ownerDocument != null && e.ownerDocument.defaultView != null ? e.ownerDocument.defaultView : window; + for (var t = $i(e.document); t instanceof e.HTMLIFrameElement; ) { + try { + var n = typeof t.contentWindow.location.href == "string"; + } catch { + n = !1; + } + if (n) e = t.contentWindow; + else break; + t = $i(e.document); + } + return t; + } + function ou(e) { + var t = e && e.nodeName && e.nodeName.toLowerCase(); + return t && (t === "input" && (e.type === "text" || e.type === "search" || e.type === "tel" || e.type === "url" || e.type === "password") || t === "textarea" || e.contentEditable === "true"); + } + var w0 = bn && "documentMode" in document && 11 >= document.documentMode, Pl = null, du = null, Za = null, hu = !1; + function Ho(e, t, n) { + var a = n.window === n ? n.document : n.nodeType === 9 ? n : n.ownerDocument; + hu || Pl == null || Pl !== $i(a) || (a = Pl, "selectionStart" in a && ou(a) ? a = { start: a.selectionStart, end: a.selectionEnd } : (a = (a.ownerDocument && a.ownerDocument.defaultView || window).getSelection(), a = { + anchorNode: a.anchorNode, + anchorOffset: a.anchorOffset, + focusNode: a.focusNode, + focusOffset: a.focusOffset + }), Za && Ga(Za, a) || (Za = a, a = Vr(du, "onSelect"), 0 < a.length && (t = new tr( + "onSelect", + "select", + null, + t, + n + ), e.push({ event: t, listeners: a }), t.target = Pl))); + } + function vl(e, t) { + var n = {}; + return n[e.toLowerCase()] = t.toLowerCase(), n["Webkit" + e] = "webkit" + t, n["Moz" + e] = "moz" + t, n; + } + var Jl = { + animationend: vl("Animation", "AnimationEnd"), + animationiteration: vl("Animation", "AnimationIteration"), + animationstart: vl("Animation", "AnimationStart"), + transitionrun: vl("Transition", "TransitionRun"), + transitionstart: vl("Transition", "TransitionStart"), + transitioncancel: vl("Transition", "TransitionCancel"), + transitionend: vl("Transition", "TransitionEnd") + }, mu = {}, Bo = {}; + bn && (Bo = document.createElement("div").style, "AnimationEvent" in window || (delete Jl.animationend.animation, delete Jl.animationiteration.animation, delete Jl.animationstart.animation), "TransitionEvent" in window || delete Jl.transitionend.transition); + function Sl(e) { + if (mu[e]) return mu[e]; + if (!Jl[e]) return e; + var t = Jl[e], n; + for (n in t) + if (t.hasOwnProperty(n) && n in Bo) + return mu[e] = t[n]; + return e; + } + var _o = Sl("animationend"), Co = Sl("animationiteration"), Lo = Sl("animationstart"), A0 = Sl("transitionrun"), O0 = Sl("transitionstart"), R0 = Sl("transitioncancel"), Xo = Sl("transitionend"), Yo = /* @__PURE__ */ new Map(), pu = "abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split( + " " + ); + pu.push("scrollEnd"); + function nn(e, t) { + Yo.set(e, t), yl(t, [e]); + } + var ar = typeof reportError == "function" ? reportError : function(e) { + if (typeof window == "object" && typeof window.ErrorEvent == "function") { + var t = new window.ErrorEvent("error", { + bubbles: !0, + cancelable: !0, + message: typeof e == "object" && e !== null && typeof e.message == "string" ? String(e.message) : String(e), + error: e + }); + if (!window.dispatchEvent(t)) return; + } else if (typeof process == "object" && typeof process.emit == "function") { + process.emit("uncaughtException", e); + return; + } + console.error(e); + }, Vt = [], Fl = 0, yu = 0; + function ir() { + for (var e = Fl, t = yu = Fl = 0; t < e; ) { + var n = Vt[t]; + Vt[t++] = null; + var a = Vt[t]; + Vt[t++] = null; + var r = Vt[t]; + Vt[t++] = null; + var u = Vt[t]; + if (Vt[t++] = null, a !== null && r !== null) { + var f = a.pending; + f === null ? r.next = r : (r.next = f.next, f.next = r), a.pending = r; + } + u !== 0 && Qo(n, r, u); + } + } + function rr(e, t, n, a) { + Vt[Fl++] = e, Vt[Fl++] = t, Vt[Fl++] = n, Vt[Fl++] = a, yu |= a, e.lanes |= a, e = e.alternate, e !== null && (e.lanes |= a); + } + function gu(e, t, n, a) { + return rr(e, t, n, a), sr(e); + } + function bl(e, t) { + return rr(e, null, null, t), sr(e); + } + function Qo(e, t, n) { + e.lanes |= n; + var a = e.alternate; + a !== null && (a.lanes |= n); + for (var r = !1, u = e.return; u !== null; ) + u.childLanes |= n, a = u.alternate, a !== null && (a.childLanes |= n), u.tag === 22 && (e = u.stateNode, e === null || e._visibility & 1 || (r = !0)), e = u, u = u.return; + return e.tag === 3 ? (u = e.stateNode, r && t !== null && (r = 31 - Nt(n), e = u.hiddenUpdates, a = e[r], a === null ? e[r] = [t] : a.push(t), t.lane = n | 536870912), u) : null; + } + function sr(e) { + if (50 < di) + throw di = 0, zc = null, Error(c(185)); + for (var t = e.return; t !== null; ) + e = t, t = e.return; + return e.tag === 3 ? e.stateNode : null; + } + var $l = {}; + function z0(e, t, n, a) { + this.tag = e, this.key = n, this.sibling = this.child = this.return = this.stateNode = this.type = this.elementType = null, this.index = 0, this.refCleanup = this.ref = null, this.pendingProps = t, this.dependencies = this.memoizedState = this.updateQueue = this.memoizedProps = null, this.mode = a, this.subtreeFlags = this.flags = 0, this.deletions = null, this.childLanes = this.lanes = 0, this.alternate = null; + } + function jt(e, t, n, a) { + return new z0(e, t, n, a); + } + function vu(e) { + return e = e.prototype, !(!e || !e.isReactComponent); + } + function En(e, t) { + var n = e.alternate; + return n === null ? (n = jt( + e.tag, + t, + e.key, + e.mode + ), n.elementType = e.elementType, n.type = e.type, n.stateNode = e.stateNode, n.alternate = e, e.alternate = n) : (n.pendingProps = t, n.type = e.type, n.flags = 0, n.subtreeFlags = 0, n.deletions = null), n.flags = e.flags & 65011712, n.childLanes = e.childLanes, n.lanes = e.lanes, n.child = e.child, n.memoizedProps = e.memoizedProps, n.memoizedState = e.memoizedState, n.updateQueue = e.updateQueue, t = e.dependencies, n.dependencies = t === null ? null : { lanes: t.lanes, firstContext: t.firstContext }, n.sibling = e.sibling, n.index = e.index, n.ref = e.ref, n.refCleanup = e.refCleanup, n; + } + function Go(e, t) { + e.flags &= 65011714; + var n = e.alternate; + return n === null ? (e.childLanes = 0, e.lanes = t, e.child = null, e.subtreeFlags = 0, e.memoizedProps = null, e.memoizedState = null, e.updateQueue = null, e.dependencies = null, e.stateNode = null) : (e.childLanes = n.childLanes, e.lanes = n.lanes, e.child = n.child, e.subtreeFlags = 0, e.deletions = null, e.memoizedProps = n.memoizedProps, e.memoizedState = n.memoizedState, e.updateQueue = n.updateQueue, e.type = n.type, t = n.dependencies, e.dependencies = t === null ? null : { + lanes: t.lanes, + firstContext: t.firstContext + }), e; + } + function ur(e, t, n, a, r, u) { + var f = 0; + if (a = e, typeof e == "function") vu(e) && (f = 1); + else if (typeof e == "string") + f = xv( + e, + n, + ae.current + ) ? 26 : e === "html" || e === "head" || e === "body" ? 27 : 5; + else + e: switch (e) { + case ue: + return e = jt(31, n, t, r), e.elementType = ue, e.lanes = u, e; + case Z: + return El(n.children, r, u, t); + case oe: + f = 8, r |= 24; + break; + case C: + return e = jt(12, n, t, r | 2), e.elementType = C, e.lanes = u, e; + case Be: + return e = jt(13, n, t, r), e.elementType = Be, e.lanes = u, e; + case K: + return e = jt(19, n, t, r), e.elementType = K, e.lanes = u, e; + default: + if (typeof e == "object" && e !== null) + switch (e.$$typeof) { + case me: + f = 10; + break e; + case ce: + f = 9; + break e; + case fe: + f = 11; + break e; + case I: + f = 14; + break e; + case ne: + f = 16, a = null; + break e; + } + f = 29, n = Error( + c(130, e === null ? "null" : typeof e, "") + ), a = null; + } + return t = jt(f, n, t, r), t.elementType = e, t.type = a, t.lanes = u, t; + } + function El(e, t, n, a) { + return e = jt(7, e, a, t), e.lanes = n, e; + } + function Su(e, t, n) { + return e = jt(6, e, null, t), e.lanes = n, e; + } + function Zo(e) { + var t = jt(18, null, null, 0); + return t.stateNode = e, t; + } + function bu(e, t, n) { + return t = jt( + 4, + e.children !== null ? e.children : [], + e.key, + t + ), t.lanes = n, t.stateNode = { + containerInfo: e.containerInfo, + pendingChildren: null, + implementation: e.implementation + }, t; + } + var Vo = /* @__PURE__ */ new WeakMap(); + function Kt(e, t) { + if (typeof e == "object" && e !== null) { + var n = Vo.get(e); + return n !== void 0 ? n : (t = { + value: e, + source: t, + stack: ge(t) + }, Vo.set(e, t), t); + } + return { + value: e, + source: t, + stack: ge(t) + }; + } + var Wl = [], kl = 0, cr = null, Va = 0, Pt = [], Jt = 0, Yn = null, cn = 1, fn = ""; + function wn(e, t) { + Wl[kl++] = Va, Wl[kl++] = cr, cr = e, Va = t; + } + function Ko(e, t, n) { + Pt[Jt++] = cn, Pt[Jt++] = fn, Pt[Jt++] = Yn, Yn = e; + var a = cn; + e = fn; + var r = 32 - Nt(a) - 1; + a &= ~(1 << r), n += 1; + var u = 32 - Nt(t) + r; + if (30 < u) { + var f = r - r % 5; + u = (a & (1 << f) - 1).toString(32), a >>= f, r -= f, cn = 1 << 32 - Nt(t) + r | n << r | a, fn = u + e; + } else + cn = 1 << u | n << r | a, fn = e; + } + function Eu(e) { + e.return !== null && (wn(e, 1), Ko(e, 1, 0)); + } + function wu(e) { + for (; e === cr; ) + cr = Wl[--kl], Wl[kl] = null, Va = Wl[--kl], Wl[kl] = null; + for (; e === Yn; ) + Yn = Pt[--Jt], Pt[Jt] = null, fn = Pt[--Jt], Pt[Jt] = null, cn = Pt[--Jt], Pt[Jt] = null; + } + function Po(e, t) { + Pt[Jt++] = cn, Pt[Jt++] = fn, Pt[Jt++] = Yn, cn = t.id, fn = t.overflow, Yn = e; + } + var ct = null, je = null, ve = !1, Qn = null, Ft = !1, Au = Error(c(519)); + function Gn(e) { + var t = Error( + c( + 418, + 1 < arguments.length && arguments[1] !== void 0 && arguments[1] ? "text" : "HTML", + "" + ) + ); + throw Ka(Kt(t, e)), Au; + } + function Jo(e) { + var t = e.stateNode, n = e.type, a = e.memoizedProps; + switch (t[ut] = e, t[bt] = a, n) { + case "dialog": + he("cancel", t), he("close", t); + break; + case "iframe": + case "object": + case "embed": + he("load", t); + break; + case "video": + case "audio": + for (n = 0; n < mi.length; n++) + he(mi[n], t); + break; + case "source": + he("error", t); + break; + case "img": + case "image": + case "link": + he("error", t), he("load", t); + break; + case "details": + he("toggle", t); + break; + case "input": + he("invalid", t), ro( + t, + a.value, + a.defaultValue, + a.checked, + a.defaultChecked, + a.type, + a.name, + !0 + ); + break; + case "select": + he("invalid", t); + break; + case "textarea": + he("invalid", t), uo(t, a.value, a.defaultValue, a.children); + } + n = a.children, typeof n != "string" && typeof n != "number" && typeof n != "bigint" || t.textContent === "" + n || a.suppressHydrationWarning === !0 || om(t.textContent, n) ? (a.popover != null && (he("beforetoggle", t), he("toggle", t)), a.onScroll != null && he("scroll", t), a.onScrollEnd != null && he("scrollend", t), a.onClick != null && (t.onclick = Sn), t = !0) : t = !1, t || Gn(e, !0); + } + function Fo(e) { + for (ct = e.return; ct; ) + switch (ct.tag) { + case 5: + case 31: + case 13: + Ft = !1; + return; + case 27: + case 3: + Ft = !0; + return; + default: + ct = ct.return; + } + } + function Il(e) { + if (e !== ct) return !1; + if (!ve) return Fo(e), ve = !0, !1; + var t = e.tag, n; + if ((n = t !== 3 && t !== 27) && ((n = t === 5) && (n = e.type, n = !(n !== "form" && n !== "button") || Yc(e.type, e.memoizedProps)), n = !n), n && je && Gn(e), Fo(e), t === 13) { + if (e = e.memoizedState, e = e !== null ? e.dehydrated : null, !e) throw Error(c(317)); + je = bm(e); + } else if (t === 31) { + if (e = e.memoizedState, e = e !== null ? e.dehydrated : null, !e) throw Error(c(317)); + je = bm(e); + } else + t === 27 ? (t = je, ll(e.type) ? (e = Kc, Kc = null, je = e) : je = t) : je = ct ? Wt(e.stateNode.nextSibling) : null; + return !0; + } + function wl() { + je = ct = null, ve = !1; + } + function Ou() { + var e = Qn; + return e !== null && (Rt === null ? Rt = e : Rt.push.apply( + Rt, + e + ), Qn = null), e; + } + function Ka(e) { + Qn === null ? Qn = [e] : Qn.push(e); + } + var Ru = Ee(null), Al = null, An = null; + function Zn(e, t, n) { + ee(Ru, t._currentValue), t._currentValue = n; + } + function On(e) { + e._currentValue = Ru.current, J(Ru); + } + function zu(e, t, n) { + for (; e !== null; ) { + var a = e.alternate; + if ((e.childLanes & t) !== t ? (e.childLanes |= t, a !== null && (a.childLanes |= t)) : a !== null && (a.childLanes & t) !== t && (a.childLanes |= t), e === n) break; + e = e.return; + } + } + function Du(e, t, n, a) { + var r = e.child; + for (r !== null && (r.return = e); r !== null; ) { + var u = r.dependencies; + if (u !== null) { + var f = r.child; + u = u.firstContext; + e: for (; u !== null; ) { + var h = u; + u = r; + for (var y = 0; y < t.length; y++) + if (h.context === t[y]) { + u.lanes |= n, h = u.alternate, h !== null && (h.lanes |= n), zu( + u.return, + n, + e + ), a || (f = null); + break e; + } + u = h.next; + } + } else if (r.tag === 18) { + if (f = r.return, f === null) throw Error(c(341)); + f.lanes |= n, u = f.alternate, u !== null && (u.lanes |= n), zu(f, n, e), f = null; + } else f = r.child; + if (f !== null) f.return = r; + else + for (f = r; f !== null; ) { + if (f === e) { + f = null; + break; + } + if (r = f.sibling, r !== null) { + r.return = f.return, f = r; + break; + } + f = f.return; + } + r = f; + } + } + function ea(e, t, n, a) { + e = null; + for (var r = t, u = !1; r !== null; ) { + if (!u) { + if ((r.flags & 524288) !== 0) u = !0; + else if ((r.flags & 262144) !== 0) break; + } + if (r.tag === 10) { + var f = r.alternate; + if (f === null) throw Error(c(387)); + if (f = f.memoizedProps, f !== null) { + var h = r.type; + qt(r.pendingProps.value, f.value) || (e !== null ? e.push(h) : e = [h]); + } + } else if (r === ht.current) { + if (f = r.alternate, f === null) throw Error(c(387)); + f.memoizedState.memoizedState !== r.memoizedState.memoizedState && (e !== null ? e.push(Si) : e = [Si]); + } + r = r.return; + } + e !== null && Du( + t, + e, + n, + a + ), t.flags |= 262144; + } + function fr(e) { + for (e = e.firstContext; e !== null; ) { + if (!qt( + e.context._currentValue, + e.memoizedValue + )) + return !0; + e = e.next; + } + return !1; + } + function Ol(e) { + Al = e, An = null, e = e.dependencies, e !== null && (e.firstContext = null); + } + function ft(e) { + return $o(Al, e); + } + function or(e, t) { + return Al === null && Ol(e), $o(e, t); + } + function $o(e, t) { + var n = t._currentValue; + if (t = { context: t, memoizedValue: n, next: null }, An === null) { + if (e === null) throw Error(c(308)); + An = t, e.dependencies = { lanes: 0, firstContext: t }, e.flags |= 524288; + } else An = An.next = t; + return n; + } + var D0 = typeof AbortController < "u" ? AbortController : function() { + var e = [], t = this.signal = { + aborted: !1, + addEventListener: function(n, a) { + e.push(a); + } + }; + this.abort = function() { + t.aborted = !0, e.forEach(function(n) { + return n(); + }); + }; + }, M0 = l.unstable_scheduleCallback, T0 = l.unstable_NormalPriority, $e = { + $$typeof: me, + Consumer: null, + Provider: null, + _currentValue: null, + _currentValue2: null, + _threadCount: 0 + }; + function Mu() { + return { + controller: new D0(), + data: /* @__PURE__ */ new Map(), + refCount: 0 + }; + } + function Pa(e) { + e.refCount--, e.refCount === 0 && M0(T0, function() { + e.controller.abort(); + }); + } + var Ja = null, Tu = 0, ta = 0, na = null; + function U0(e, t) { + if (Ja === null) { + var n = Ja = []; + Tu = 0, ta = Nc(), na = { + status: "pending", + value: void 0, + then: function(a) { + n.push(a); + } + }; + } + return Tu++, t.then(Wo, Wo), t; + } + function Wo() { + if (--Tu === 0 && Ja !== null) { + na !== null && (na.status = "fulfilled"); + var e = Ja; + Ja = null, ta = 0, na = null; + for (var t = 0; t < e.length; t++) (0, e[t])(); + } + } + function x0(e, t) { + var n = [], a = { + status: "pending", + value: null, + reason: null, + then: function(r) { + n.push(r); + } + }; + return e.then( + function() { + a.status = "fulfilled", a.value = t; + for (var r = 0; r < n.length; r++) (0, n[r])(t); + }, + function(r) { + for (a.status = "rejected", a.reason = r, r = 0; r < n.length; r++) + (0, n[r])(void 0); + } + ), a; + } + var ko = b.S; + b.S = function(e, t) { + Hh = st(), typeof t == "object" && t !== null && typeof t.then == "function" && U0(e, t), ko !== null && ko(e, t); + }; + var Rl = Ee(null); + function Uu() { + var e = Rl.current; + return e !== null ? e : Ne.pooledCache; + } + function dr(e, t) { + t === null ? ee(Rl, Rl.current) : ee(Rl, t.pool); + } + function Io() { + var e = Uu(); + return e === null ? null : { parent: $e._currentValue, pool: e }; + } + var la = Error(c(460)), xu = Error(c(474)), hr = Error(c(542)), mr = { then: function() { + } }; + function ed(e) { + return e = e.status, e === "fulfilled" || e === "rejected"; + } + function td(e, t, n) { + switch (n = e[n], n === void 0 ? e.push(t) : n !== t && (t.then(Sn, Sn), t = n), t.status) { + case "fulfilled": + return t.value; + case "rejected": + throw e = t.reason, ld(e), e; + default: + if (typeof t.status == "string") t.then(Sn, Sn); + else { + if (e = Ne, e !== null && 100 < e.shellSuspendCounter) + throw Error(c(482)); + e = t, e.status = "pending", e.then( + function(a) { + if (t.status === "pending") { + var r = t; + r.status = "fulfilled", r.value = a; + } + }, + function(a) { + if (t.status === "pending") { + var r = t; + r.status = "rejected", r.reason = a; + } + } + ); + } + switch (t.status) { + case "fulfilled": + return t.value; + case "rejected": + throw e = t.reason, ld(e), e; + } + throw Dl = t, la; + } + } + function zl(e) { + try { + var t = e._init; + return t(e._payload); + } catch (n) { + throw n !== null && typeof n == "object" && typeof n.then == "function" ? (Dl = n, la) : n; + } + } + var Dl = null; + function nd() { + if (Dl === null) throw Error(c(459)); + var e = Dl; + return Dl = null, e; + } + function ld(e) { + if (e === la || e === hr) + throw Error(c(483)); + } + var aa = null, Fa = 0; + function pr(e) { + var t = Fa; + return Fa += 1, aa === null && (aa = []), td(aa, e, t); + } + function $a(e, t) { + t = t.props.ref, e.ref = t !== void 0 ? t : null; + } + function yr(e, t) { + throw t.$$typeof === x ? Error(c(525)) : (e = Object.prototype.toString.call(t), Error( + c( + 31, + e === "[object Object]" ? "object with keys {" + Object.keys(t).join(", ") + "}" : e + ) + )); + } + function ad(e) { + function t(E, v) { + if (e) { + var O = E.deletions; + O === null ? (E.deletions = [v], E.flags |= 16) : O.push(v); + } + } + function n(E, v) { + if (!e) return null; + for (; v !== null; ) + t(E, v), v = v.sibling; + return null; + } + function a(E) { + for (var v = /* @__PURE__ */ new Map(); E !== null; ) + E.key !== null ? v.set(E.key, E) : v.set(E.index, E), E = E.sibling; + return v; + } + function r(E, v) { + return E = En(E, v), E.index = 0, E.sibling = null, E; + } + function u(E, v, O) { + return E.index = O, e ? (O = E.alternate, O !== null ? (O = O.index, O < v ? (E.flags |= 67108866, v) : O) : (E.flags |= 67108866, v)) : (E.flags |= 1048576, v); + } + function f(E) { + return e && E.alternate === null && (E.flags |= 67108866), E; + } + function h(E, v, O, T) { + return v === null || v.tag !== 6 ? (v = Su(O, E.mode, T), v.return = E, v) : (v = r(v, O), v.return = E, v); + } + function y(E, v, O, T) { + var F = O.type; + return F === Z ? M( + E, + v, + O.props.children, + T, + O.key + ) : v !== null && (v.elementType === F || typeof F == "object" && F !== null && F.$$typeof === ne && zl(F) === v.type) ? (v = r(v, O.props), $a(v, O), v.return = E, v) : (v = ur( + O.type, + O.key, + O.props, + null, + E.mode, + T + ), $a(v, O), v.return = E, v); + } + function R(E, v, O, T) { + return v === null || v.tag !== 4 || v.stateNode.containerInfo !== O.containerInfo || v.stateNode.implementation !== O.implementation ? (v = bu(O, E.mode, T), v.return = E, v) : (v = r(v, O.children || []), v.return = E, v); + } + function M(E, v, O, T, F) { + return v === null || v.tag !== 7 ? (v = El( + O, + E.mode, + T, + F + ), v.return = E, v) : (v = r(v, O), v.return = E, v); + } + function U(E, v, O) { + if (typeof v == "string" && v !== "" || typeof v == "number" || typeof v == "bigint") + return v = Su( + "" + v, + E.mode, + O + ), v.return = E, v; + if (typeof v == "object" && v !== null) { + switch (v.$$typeof) { + case _: + return O = ur( + v.type, + v.key, + v.props, + null, + E.mode, + O + ), $a(O, v), O.return = E, O; + case B: + return v = bu( + v, + E.mode, + O + ), v.return = E, v; + case ne: + return v = zl(v), U(E, v, O); + } + if (be(v) || H(v)) + return v = El( + v, + E.mode, + O, + null + ), v.return = E, v; + if (typeof v.then == "function") + return U(E, pr(v), O); + if (v.$$typeof === me) + return U( + E, + or(E, v), + O + ); + yr(E, v); + } + return null; + } + function z(E, v, O, T) { + var F = v !== null ? v.key : null; + if (typeof O == "string" && O !== "" || typeof O == "number" || typeof O == "bigint") + return F !== null ? null : h(E, v, "" + O, T); + if (typeof O == "object" && O !== null) { + switch (O.$$typeof) { + case _: + return O.key === F ? y(E, v, O, T) : null; + case B: + return O.key === F ? R(E, v, O, T) : null; + case ne: + return O = zl(O), z(E, v, O, T); + } + if (be(O) || H(O)) + return F !== null ? null : M(E, v, O, T, null); + if (typeof O.then == "function") + return z( + E, + v, + pr(O), + T + ); + if (O.$$typeof === me) + return z( + E, + v, + or(E, O), + T + ); + yr(E, O); + } + return null; + } + function D(E, v, O, T, F) { + if (typeof T == "string" && T !== "" || typeof T == "number" || typeof T == "bigint") + return E = E.get(O) || null, h(v, E, "" + T, F); + if (typeof T == "object" && T !== null) { + switch (T.$$typeof) { + case _: + return E = E.get( + T.key === null ? O : T.key + ) || null, y(v, E, T, F); + case B: + return E = E.get( + T.key === null ? O : T.key + ) || null, R(v, E, T, F); + case ne: + return T = zl(T), D( + E, + v, + O, + T, + F + ); + } + if (be(T) || H(T)) + return E = E.get(O) || null, M(v, E, T, F, null); + if (typeof T.then == "function") + return D( + E, + v, + O, + pr(T), + F + ); + if (T.$$typeof === me) + return D( + E, + v, + O, + or(v, T), + F + ); + yr(v, T); + } + return null; + } + function X(E, v, O, T) { + for (var F = null, Ae = null, G = v, se = v = 0, ye = null; G !== null && se < O.length; se++) { + G.index > se ? (ye = G, G = null) : ye = G.sibling; + var Oe = z( + E, + G, + O[se], + T + ); + if (Oe === null) { + G === null && (G = ye); + break; + } + e && G && Oe.alternate === null && t(E, G), v = u(Oe, v, se), Ae === null ? F = Oe : Ae.sibling = Oe, Ae = Oe, G = ye; + } + if (se === O.length) + return n(E, G), ve && wn(E, se), F; + if (G === null) { + for (; se < O.length; se++) + G = U(E, O[se], T), G !== null && (v = u( + G, + v, + se + ), Ae === null ? F = G : Ae.sibling = G, Ae = G); + return ve && wn(E, se), F; + } + for (G = a(G); se < O.length; se++) + ye = D( + G, + E, + se, + O[se], + T + ), ye !== null && (e && ye.alternate !== null && G.delete( + ye.key === null ? se : ye.key + ), v = u( + ye, + v, + se + ), Ae === null ? F = ye : Ae.sibling = ye, Ae = ye); + return e && G.forEach(function(ul) { + return t(E, ul); + }), ve && wn(E, se), F; + } + function k(E, v, O, T) { + if (O == null) throw Error(c(151)); + for (var F = null, Ae = null, G = v, se = v = 0, ye = null, Oe = O.next(); G !== null && !Oe.done; se++, Oe = O.next()) { + G.index > se ? (ye = G, G = null) : ye = G.sibling; + var ul = z(E, G, Oe.value, T); + if (ul === null) { + G === null && (G = ye); + break; + } + e && G && ul.alternate === null && t(E, G), v = u(ul, v, se), Ae === null ? F = ul : Ae.sibling = ul, Ae = ul, G = ye; + } + if (Oe.done) + return n(E, G), ve && wn(E, se), F; + if (G === null) { + for (; !Oe.done; se++, Oe = O.next()) + Oe = U(E, Oe.value, T), Oe !== null && (v = u(Oe, v, se), Ae === null ? F = Oe : Ae.sibling = Oe, Ae = Oe); + return ve && wn(E, se), F; + } + for (G = a(G); !Oe.done; se++, Oe = O.next()) + Oe = D(G, E, se, Oe.value, T), Oe !== null && (e && Oe.alternate !== null && G.delete(Oe.key === null ? se : Oe.key), v = u(Oe, v, se), Ae === null ? F = Oe : Ae.sibling = Oe, Ae = Oe); + return e && G.forEach(function(Qv) { + return t(E, Qv); + }), ve && wn(E, se), F; + } + function Ue(E, v, O, T) { + if (typeof O == "object" && O !== null && O.type === Z && O.key === null && (O = O.props.children), typeof O == "object" && O !== null) { + switch (O.$$typeof) { + case _: + e: { + for (var F = O.key; v !== null; ) { + if (v.key === F) { + if (F = O.type, F === Z) { + if (v.tag === 7) { + n( + E, + v.sibling + ), T = r( + v, + O.props.children + ), T.return = E, E = T; + break e; + } + } else if (v.elementType === F || typeof F == "object" && F !== null && F.$$typeof === ne && zl(F) === v.type) { + n( + E, + v.sibling + ), T = r(v, O.props), $a(T, O), T.return = E, E = T; + break e; + } + n(E, v); + break; + } else t(E, v); + v = v.sibling; + } + O.type === Z ? (T = El( + O.props.children, + E.mode, + T, + O.key + ), T.return = E, E = T) : (T = ur( + O.type, + O.key, + O.props, + null, + E.mode, + T + ), $a(T, O), T.return = E, E = T); + } + return f(E); + case B: + e: { + for (F = O.key; v !== null; ) { + if (v.key === F) + if (v.tag === 4 && v.stateNode.containerInfo === O.containerInfo && v.stateNode.implementation === O.implementation) { + n( + E, + v.sibling + ), T = r(v, O.children || []), T.return = E, E = T; + break e; + } else { + n(E, v); + break; + } + else t(E, v); + v = v.sibling; + } + T = bu(O, E.mode, T), T.return = E, E = T; + } + return f(E); + case ne: + return O = zl(O), Ue( + E, + v, + O, + T + ); + } + if (be(O)) + return X( + E, + v, + O, + T + ); + if (H(O)) { + if (F = H(O), typeof F != "function") throw Error(c(150)); + return O = F.call(O), k( + E, + v, + O, + T + ); + } + if (typeof O.then == "function") + return Ue( + E, + v, + pr(O), + T + ); + if (O.$$typeof === me) + return Ue( + E, + v, + or(E, O), + T + ); + yr(E, O); + } + return typeof O == "string" && O !== "" || typeof O == "number" || typeof O == "bigint" ? (O = "" + O, v !== null && v.tag === 6 ? (n(E, v.sibling), T = r(v, O), T.return = E, E = T) : (n(E, v), T = Su(O, E.mode, T), T.return = E, E = T), f(E)) : n(E, v); + } + return function(E, v, O, T) { + try { + Fa = 0; + var F = Ue( + E, + v, + O, + T + ); + return aa = null, F; + } catch (G) { + if (G === la || G === hr) throw G; + var Ae = jt(29, G, null, E.mode); + return Ae.lanes = T, Ae.return = E, Ae; + } + }; + } + var Ml = ad(!0), id = ad(!1), Vn = !1; + function Nu(e) { + e.updateQueue = { + baseState: e.memoizedState, + firstBaseUpdate: null, + lastBaseUpdate: null, + shared: { pending: null, lanes: 0, hiddenCallbacks: null }, + callbacks: null + }; + } + function qu(e, t) { + e = e.updateQueue, t.updateQueue === e && (t.updateQueue = { + baseState: e.baseState, + firstBaseUpdate: e.firstBaseUpdate, + lastBaseUpdate: e.lastBaseUpdate, + shared: e.shared, + callbacks: null + }); + } + function Kn(e) { + return { lane: e, tag: 0, payload: null, callback: null, next: null }; + } + function Pn(e, t, n) { + var a = e.updateQueue; + if (a === null) return null; + if (a = a.shared, (Re & 2) !== 0) { + var r = a.pending; + return r === null ? t.next = t : (t.next = r.next, r.next = t), a.pending = t, t = sr(e), Qo(e, null, n), t; + } + return rr(e, a, t, n), sr(e); + } + function Wa(e, t, n) { + if (t = t.updateQueue, t !== null && (t = t.shared, (n & 4194048) !== 0)) { + var a = t.lanes; + a &= e.pendingLanes, n |= a, t.lanes = n, Ff(e, n); + } + } + function ju(e, t) { + var n = e.updateQueue, a = e.alternate; + if (a !== null && (a = a.updateQueue, n === a)) { + var r = null, u = null; + if (n = n.firstBaseUpdate, n !== null) { + do { + var f = { + lane: n.lane, + tag: n.tag, + payload: n.payload, + callback: null, + next: null + }; + u === null ? r = u = f : u = u.next = f, n = n.next; + } while (n !== null); + u === null ? r = u = t : u = u.next = t; + } else r = u = t; + n = { + baseState: a.baseState, + firstBaseUpdate: r, + lastBaseUpdate: u, + shared: a.shared, + callbacks: a.callbacks + }, e.updateQueue = n; + return; + } + e = n.lastBaseUpdate, e === null ? n.firstBaseUpdate = t : e.next = t, n.lastBaseUpdate = t; + } + var Hu = !1; + function ka() { + if (Hu) { + var e = na; + if (e !== null) throw e; + } + } + function Ia(e, t, n, a) { + Hu = !1; + var r = e.updateQueue; + Vn = !1; + var u = r.firstBaseUpdate, f = r.lastBaseUpdate, h = r.shared.pending; + if (h !== null) { + r.shared.pending = null; + var y = h, R = y.next; + y.next = null, f === null ? u = R : f.next = R, f = y; + var M = e.alternate; + M !== null && (M = M.updateQueue, h = M.lastBaseUpdate, h !== f && (h === null ? M.firstBaseUpdate = R : h.next = R, M.lastBaseUpdate = y)); + } + if (u !== null) { + var U = r.baseState; + f = 0, M = R = y = null, h = u; + do { + var z = h.lane & -536870913, D = z !== h.lane; + if (D ? (pe & z) === z : (a & z) === z) { + z !== 0 && z === ta && (Hu = !0), M !== null && (M = M.next = { + lane: 0, + tag: h.tag, + payload: h.payload, + callback: null, + next: null + }); + e: { + var X = e, k = h; + z = t; + var Ue = n; + switch (k.tag) { + case 1: + if (X = k.payload, typeof X == "function") { + U = X.call(Ue, U, z); + break e; + } + U = X; + break e; + case 3: + X.flags = X.flags & -65537 | 128; + case 0: + if (X = k.payload, z = typeof X == "function" ? X.call(Ue, U, z) : X, z == null) break e; + U = w({}, U, z); + break e; + case 2: + Vn = !0; + } + } + z = h.callback, z !== null && (e.flags |= 64, D && (e.flags |= 8192), D = r.callbacks, D === null ? r.callbacks = [z] : D.push(z)); + } else + D = { + lane: z, + tag: h.tag, + payload: h.payload, + callback: h.callback, + next: null + }, M === null ? (R = M = D, y = U) : M = M.next = D, f |= z; + if (h = h.next, h === null) { + if (h = r.shared.pending, h === null) + break; + D = h, h = D.next, D.next = null, r.lastBaseUpdate = D, r.shared.pending = null; + } + } while (!0); + M === null && (y = U), r.baseState = y, r.firstBaseUpdate = R, r.lastBaseUpdate = M, u === null && (r.shared.lanes = 0), kn |= f, e.lanes = f, e.memoizedState = U; + } + } + function rd(e, t) { + if (typeof e != "function") + throw Error(c(191, e)); + e.call(t); + } + function sd(e, t) { + var n = e.callbacks; + if (n !== null) + for (e.callbacks = null, e = 0; e < n.length; e++) + rd(n[e], t); + } + var ia = Ee(null), gr = Ee(0); + function ud(e, t) { + e = qn, ee(gr, e), ee(ia, t), qn = e | t.baseLanes; + } + function Bu() { + ee(gr, qn), ee(ia, ia.current); + } + function _u() { + qn = gr.current, J(ia), J(gr); + } + var Ht = Ee(null), $t = null; + function Jn(e) { + var t = e.alternate; + ee(Ve, Ve.current & 1), ee(Ht, e), $t === null && (t === null || ia.current !== null || t.memoizedState !== null) && ($t = e); + } + function Cu(e) { + ee(Ve, Ve.current), ee(Ht, e), $t === null && ($t = e); + } + function cd(e) { + e.tag === 22 ? (ee(Ve, Ve.current), ee(Ht, e), $t === null && ($t = e)) : Fn(); + } + function Fn() { + ee(Ve, Ve.current), ee(Ht, Ht.current); + } + function Bt(e) { + J(Ht), $t === e && ($t = null), J(Ve); + } + var Ve = Ee(0); + function vr(e) { + for (var t = e; t !== null; ) { + if (t.tag === 13) { + var n = t.memoizedState; + if (n !== null && (n = n.dehydrated, n === null || Zc(n) || Vc(n))) + return t; + } else if (t.tag === 19 && (t.memoizedProps.revealOrder === "forwards" || t.memoizedProps.revealOrder === "backwards" || t.memoizedProps.revealOrder === "unstable_legacy-backwards" || t.memoizedProps.revealOrder === "together")) { + if ((t.flags & 128) !== 0) return t; + } else if (t.child !== null) { + t.child.return = t, t = t.child; + continue; + } + if (t === e) break; + for (; t.sibling === null; ) { + if (t.return === null || t.return === e) return null; + t = t.return; + } + t.sibling.return = t.return, t = t.sibling; + } + return null; + } + var Rn = 0, re = null, Me = null, We = null, Sr = !1, ra = !1, Tl = !1, br = 0, ei = 0, sa = null, N0 = 0; + function Ye() { + throw Error(c(321)); + } + function Lu(e, t) { + if (t === null) return !1; + for (var n = 0; n < t.length && n < e.length; n++) + if (!qt(e[n], t[n])) return !1; + return !0; + } + function Xu(e, t, n, a, r, u) { + return Rn = u, re = t, t.memoizedState = null, t.updateQueue = null, t.lanes = 0, b.H = e === null || e.memoizedState === null ? Kd : tc, Tl = !1, u = n(a, r), Tl = !1, ra && (u = od( + t, + n, + a, + r + )), fd(e), u; + } + function fd(e) { + b.H = li; + var t = Me !== null && Me.next !== null; + if (Rn = 0, We = Me = re = null, Sr = !1, ei = 0, sa = null, t) throw Error(c(300)); + e === null || ke || (e = e.dependencies, e !== null && fr(e) && (ke = !0)); + } + function od(e, t, n, a) { + re = e; + var r = 0; + do { + if (ra && (sa = null), ei = 0, ra = !1, 25 <= r) throw Error(c(301)); + if (r += 1, We = Me = null, e.updateQueue != null) { + var u = e.updateQueue; + u.lastEffect = null, u.events = null, u.stores = null, u.memoCache != null && (u.memoCache.index = 0); + } + b.H = Pd, u = t(n, a); + } while (ra); + return u; + } + function q0() { + var e = b.H, t = e.useState()[0]; + return t = typeof t.then == "function" ? ti(t) : t, e = e.useState()[0], (Me !== null ? Me.memoizedState : null) !== e && (re.flags |= 1024), t; + } + function Yu() { + var e = br !== 0; + return br = 0, e; + } + function Qu(e, t, n) { + t.updateQueue = e.updateQueue, t.flags &= -2053, e.lanes &= ~n; + } + function Gu(e) { + if (Sr) { + for (e = e.memoizedState; e !== null; ) { + var t = e.queue; + t !== null && (t.pending = null), e = e.next; + } + Sr = !1; + } + Rn = 0, We = Me = re = null, ra = !1, ei = br = 0, sa = null; + } + function pt() { + var e = { + memoizedState: null, + baseState: null, + baseQueue: null, + queue: null, + next: null + }; + return We === null ? re.memoizedState = We = e : We = We.next = e, We; + } + function Ke() { + if (Me === null) { + var e = re.alternate; + e = e !== null ? e.memoizedState : null; + } else e = Me.next; + var t = We === null ? re.memoizedState : We.next; + if (t !== null) + We = t, Me = e; + else { + if (e === null) + throw re.alternate === null ? Error(c(467)) : Error(c(310)); + Me = e, e = { + memoizedState: Me.memoizedState, + baseState: Me.baseState, + baseQueue: Me.baseQueue, + queue: Me.queue, + next: null + }, We === null ? re.memoizedState = We = e : We = We.next = e; + } + return We; + } + function Er() { + return { lastEffect: null, events: null, stores: null, memoCache: null }; + } + function ti(e) { + var t = ei; + return ei += 1, sa === null && (sa = []), e = td(sa, e, t), t = re, (We === null ? t.memoizedState : We.next) === null && (t = t.alternate, b.H = t === null || t.memoizedState === null ? Kd : tc), e; + } + function wr(e) { + if (e !== null && typeof e == "object") { + if (typeof e.then == "function") return ti(e); + if (e.$$typeof === me) return ft(e); + } + throw Error(c(438, String(e))); + } + function Zu(e) { + var t = null, n = re.updateQueue; + if (n !== null && (t = n.memoCache), t == null) { + var a = re.alternate; + a !== null && (a = a.updateQueue, a !== null && (a = a.memoCache, a != null && (t = { + data: a.data.map(function(r) { + return r.slice(); + }), + index: 0 + }))); + } + if (t == null && (t = { data: [], index: 0 }), n === null && (n = Er(), re.updateQueue = n), n.memoCache = t, n = t.data[t.index], n === void 0) + for (n = t.data[t.index] = Array(e), a = 0; a < e; a++) + n[a] = V; + return t.index++, n; + } + function zn(e, t) { + return typeof t == "function" ? t(e) : t; + } + function Ar(e) { + var t = Ke(); + return Vu(t, Me, e); + } + function Vu(e, t, n) { + var a = e.queue; + if (a === null) throw Error(c(311)); + a.lastRenderedReducer = n; + var r = e.baseQueue, u = a.pending; + if (u !== null) { + if (r !== null) { + var f = r.next; + r.next = u.next, u.next = f; + } + t.baseQueue = r = u, a.pending = null; + } + if (u = e.baseState, r === null) e.memoizedState = u; + else { + t = r.next; + var h = f = null, y = null, R = t, M = !1; + do { + var U = R.lane & -536870913; + if (U !== R.lane ? (pe & U) === U : (Rn & U) === U) { + var z = R.revertLane; + if (z === 0) + y !== null && (y = y.next = { + lane: 0, + revertLane: 0, + gesture: null, + action: R.action, + hasEagerState: R.hasEagerState, + eagerState: R.eagerState, + next: null + }), U === ta && (M = !0); + else if ((Rn & z) === z) { + R = R.next, z === ta && (M = !0); + continue; + } else + U = { + lane: 0, + revertLane: R.revertLane, + gesture: null, + action: R.action, + hasEagerState: R.hasEagerState, + eagerState: R.eagerState, + next: null + }, y === null ? (h = y = U, f = u) : y = y.next = U, re.lanes |= z, kn |= z; + U = R.action, Tl && n(u, U), u = R.hasEagerState ? R.eagerState : n(u, U); + } else + z = { + lane: U, + revertLane: R.revertLane, + gesture: R.gesture, + action: R.action, + hasEagerState: R.hasEagerState, + eagerState: R.eagerState, + next: null + }, y === null ? (h = y = z, f = u) : y = y.next = z, re.lanes |= U, kn |= U; + R = R.next; + } while (R !== null && R !== t); + if (y === null ? f = u : y.next = h, !qt(u, e.memoizedState) && (ke = !0, M && (n = na, n !== null))) + throw n; + e.memoizedState = u, e.baseState = f, e.baseQueue = y, a.lastRenderedState = u; + } + return r === null && (a.lanes = 0), [e.memoizedState, a.dispatch]; + } + function Ku(e) { + var t = Ke(), n = t.queue; + if (n === null) throw Error(c(311)); + n.lastRenderedReducer = e; + var a = n.dispatch, r = n.pending, u = t.memoizedState; + if (r !== null) { + n.pending = null; + var f = r = r.next; + do + u = e(u, f.action), f = f.next; + while (f !== r); + qt(u, t.memoizedState) || (ke = !0), t.memoizedState = u, t.baseQueue === null && (t.baseState = u), n.lastRenderedState = u; + } + return [u, a]; + } + function dd(e, t, n) { + var a = re, r = Ke(), u = ve; + if (u) { + if (n === void 0) throw Error(c(407)); + n = n(); + } else n = t(); + var f = !qt( + (Me || r).memoizedState, + n + ); + if (f && (r.memoizedState = n, ke = !0), r = r.queue, Fu(pd.bind(null, a, r, e), [ + e + ]), r.getSnapshot !== t || f || We !== null && We.memoizedState.tag & 1) { + if (a.flags |= 2048, ua( + 9, + { destroy: void 0 }, + md.bind( + null, + a, + r, + n, + t + ), + null + ), Ne === null) throw Error(c(349)); + u || (Rn & 127) !== 0 || hd(a, t, n); + } + return n; + } + function hd(e, t, n) { + e.flags |= 16384, e = { getSnapshot: t, value: n }, t = re.updateQueue, t === null ? (t = Er(), re.updateQueue = t, t.stores = [e]) : (n = t.stores, n === null ? t.stores = [e] : n.push(e)); + } + function md(e, t, n, a) { + t.value = n, t.getSnapshot = a, yd(t) && gd(e); + } + function pd(e, t, n) { + return n(function() { + yd(t) && gd(e); + }); + } + function yd(e) { + var t = e.getSnapshot; + e = e.value; + try { + var n = t(); + return !qt(e, n); + } catch { + return !0; + } + } + function gd(e) { + var t = bl(e, 2); + t !== null && zt(t, e, 2); + } + function Pu(e) { + var t = pt(); + if (typeof e == "function") { + var n = e; + if (e = n(), Tl) { + Cn(!0); + try { + n(); + } finally { + Cn(!1); + } + } + } + return t.memoizedState = t.baseState = e, t.queue = { + pending: null, + lanes: 0, + dispatch: null, + lastRenderedReducer: zn, + lastRenderedState: e + }, t; + } + function vd(e, t, n, a) { + return e.baseState = n, Vu( + e, + Me, + typeof a == "function" ? a : zn + ); + } + function j0(e, t, n, a, r) { + if (zr(e)) throw Error(c(485)); + if (e = t.action, e !== null) { + var u = { + payload: r, + action: e, + next: null, + isTransition: !0, + status: "pending", + value: null, + reason: null, + listeners: [], + then: function(f) { + u.listeners.push(f); + } + }; + b.T !== null ? n(!0) : u.isTransition = !1, a(u), n = t.pending, n === null ? (u.next = t.pending = u, Sd(t, u)) : (u.next = n.next, t.pending = n.next = u); + } + } + function Sd(e, t) { + var n = t.action, a = t.payload, r = e.state; + if (t.isTransition) { + var u = b.T, f = {}; + b.T = f; + try { + var h = n(r, a), y = b.S; + y !== null && y(f, h), bd(e, t, h); + } catch (R) { + Ju(e, t, R); + } finally { + u !== null && f.types !== null && (u.types = f.types), b.T = u; + } + } else + try { + u = n(r, a), bd(e, t, u); + } catch (R) { + Ju(e, t, R); + } + } + function bd(e, t, n) { + n !== null && typeof n == "object" && typeof n.then == "function" ? n.then( + function(a) { + Ed(e, t, a); + }, + function(a) { + return Ju(e, t, a); + } + ) : Ed(e, t, n); + } + function Ed(e, t, n) { + t.status = "fulfilled", t.value = n, wd(t), e.state = n, t = e.pending, t !== null && (n = t.next, n === t ? e.pending = null : (n = n.next, t.next = n, Sd(e, n))); + } + function Ju(e, t, n) { + var a = e.pending; + if (e.pending = null, a !== null) { + a = a.next; + do + t.status = "rejected", t.reason = n, wd(t), t = t.next; + while (t !== a); + } + e.action = null; + } + function wd(e) { + e = e.listeners; + for (var t = 0; t < e.length; t++) (0, e[t])(); + } + function Ad(e, t) { + return t; + } + function Od(e, t) { + if (ve) { + var n = Ne.formState; + if (n !== null) { + e: { + var a = re; + if (ve) { + if (je) { + t: { + for (var r = je, u = Ft; r.nodeType !== 8; ) { + if (!u) { + r = null; + break t; + } + if (r = Wt( + r.nextSibling + ), r === null) { + r = null; + break t; + } + } + u = r.data, r = u === "F!" || u === "F" ? r : null; + } + if (r) { + je = Wt( + r.nextSibling + ), a = r.data === "F!"; + break e; + } + } + Gn(a); + } + a = !1; + } + a && (t = n[0]); + } + } + return n = pt(), n.memoizedState = n.baseState = t, a = { + pending: null, + lanes: 0, + dispatch: null, + lastRenderedReducer: Ad, + lastRenderedState: t + }, n.queue = a, n = Gd.bind( + null, + re, + a + ), a.dispatch = n, a = Pu(!1), u = ec.bind( + null, + re, + !1, + a.queue + ), a = pt(), r = { + state: t, + dispatch: null, + action: e, + pending: null + }, a.queue = r, n = j0.bind( + null, + re, + r, + u, + n + ), r.dispatch = n, a.memoizedState = e, [t, n, !1]; + } + function Rd(e) { + var t = Ke(); + return zd(t, Me, e); + } + function zd(e, t, n) { + if (t = Vu( + e, + t, + Ad + )[0], e = Ar(zn)[0], typeof t == "object" && t !== null && typeof t.then == "function") + try { + var a = ti(t); + } catch (f) { + throw f === la ? hr : f; + } + else a = t; + t = Ke(); + var r = t.queue, u = r.dispatch; + return n !== t.memoizedState && (re.flags |= 2048, ua( + 9, + { destroy: void 0 }, + H0.bind(null, r, n), + null + )), [a, u, e]; + } + function H0(e, t) { + e.action = t; + } + function Dd(e) { + var t = Ke(), n = Me; + if (n !== null) + return zd(t, n, e); + Ke(), t = t.memoizedState, n = Ke(); + var a = n.queue.dispatch; + return n.memoizedState = e, [t, a, !1]; + } + function ua(e, t, n, a) { + return e = { tag: e, create: n, deps: a, inst: t, next: null }, t = re.updateQueue, t === null && (t = Er(), re.updateQueue = t), n = t.lastEffect, n === null ? t.lastEffect = e.next = e : (a = n.next, n.next = e, e.next = a, t.lastEffect = e), e; + } + function Md() { + return Ke().memoizedState; + } + function Or(e, t, n, a) { + var r = pt(); + re.flags |= e, r.memoizedState = ua( + 1 | t, + { destroy: void 0 }, + n, + a === void 0 ? null : a + ); + } + function Rr(e, t, n, a) { + var r = Ke(); + a = a === void 0 ? null : a; + var u = r.memoizedState.inst; + Me !== null && a !== null && Lu(a, Me.memoizedState.deps) ? r.memoizedState = ua(t, u, n, a) : (re.flags |= e, r.memoizedState = ua( + 1 | t, + u, + n, + a + )); + } + function Td(e, t) { + Or(8390656, 8, e, t); + } + function Fu(e, t) { + Rr(2048, 8, e, t); + } + function B0(e) { + re.flags |= 4; + var t = re.updateQueue; + if (t === null) + t = Er(), re.updateQueue = t, t.events = [e]; + else { + var n = t.events; + n === null ? t.events = [e] : n.push(e); + } + } + function Ud(e) { + var t = Ke().memoizedState; + return B0({ ref: t, nextImpl: e }), function() { + if ((Re & 2) !== 0) throw Error(c(440)); + return t.impl.apply(void 0, arguments); + }; + } + function xd(e, t) { + return Rr(4, 2, e, t); + } + function Nd(e, t) { + return Rr(4, 4, e, t); + } + function qd(e, t) { + if (typeof t == "function") { + e = e(); + var n = t(e); + return function() { + typeof n == "function" ? n() : t(null); + }; + } + if (t != null) + return e = e(), t.current = e, function() { + t.current = null; + }; + } + function jd(e, t, n) { + n = n != null ? n.concat([e]) : null, Rr(4, 4, qd.bind(null, t, e), n); + } + function $u() { + } + function Hd(e, t) { + var n = Ke(); + t = t === void 0 ? null : t; + var a = n.memoizedState; + return t !== null && Lu(t, a[1]) ? a[0] : (n.memoizedState = [e, t], e); + } + function Bd(e, t) { + var n = Ke(); + t = t === void 0 ? null : t; + var a = n.memoizedState; + if (t !== null && Lu(t, a[1])) + return a[0]; + if (a = e(), Tl) { + Cn(!0); + try { + e(); + } finally { + Cn(!1); + } + } + return n.memoizedState = [a, t], a; + } + function Wu(e, t, n) { + return n === void 0 || (Rn & 1073741824) !== 0 && (pe & 261930) === 0 ? e.memoizedState = t : (e.memoizedState = n, e = _h(), re.lanes |= e, kn |= e, n); + } + function _d(e, t, n, a) { + return qt(n, t) ? n : ia.current !== null ? (e = Wu(e, n, a), qt(e, t) || (ke = !0), e) : (Rn & 42) === 0 || (Rn & 1073741824) !== 0 && (pe & 261930) === 0 ? (ke = !0, e.memoizedState = n) : (e = _h(), re.lanes |= e, kn |= e, t); + } + function Cd(e, t, n, a, r) { + var u = N.p; + N.p = u !== 0 && 8 > u ? u : 8; + var f = b.T, h = {}; + b.T = h, ec(e, !1, t, n); + try { + var y = r(), R = b.S; + if (R !== null && R(h, y), y !== null && typeof y == "object" && typeof y.then == "function") { + var M = x0( + y, + a + ); + ni( + e, + t, + M, + Lt(e) + ); + } else + ni( + e, + t, + a, + Lt(e) + ); + } catch (U) { + ni( + e, + t, + { then: function() { + }, status: "rejected", reason: U }, + Lt() + ); + } finally { + N.p = u, f !== null && h.types !== null && (f.types = h.types), b.T = f; + } + } + function _0() { + } + function ku(e, t, n, a) { + if (e.tag !== 5) throw Error(c(476)); + var r = Ld(e).queue; + Cd( + e, + r, + t, + L, + n === null ? _0 : function() { + return Xd(e), n(a); + } + ); + } + function Ld(e) { + var t = e.memoizedState; + if (t !== null) return t; + t = { + memoizedState: L, + baseState: L, + baseQueue: null, + queue: { + pending: null, + lanes: 0, + dispatch: null, + lastRenderedReducer: zn, + lastRenderedState: L + }, + next: null + }; + var n = {}; + return t.next = { + memoizedState: n, + baseState: n, + baseQueue: null, + queue: { + pending: null, + lanes: 0, + dispatch: null, + lastRenderedReducer: zn, + lastRenderedState: n + }, + next: null + }, e.memoizedState = t, e = e.alternate, e !== null && (e.memoizedState = t), t; + } + function Xd(e) { + var t = Ld(e); + t.next === null && (t = e.alternate.memoizedState), ni( + e, + t.next.queue, + {}, + Lt() + ); + } + function Iu() { + return ft(Si); + } + function Yd() { + return Ke().memoizedState; + } + function Qd() { + return Ke().memoizedState; + } + function C0(e) { + for (var t = e.return; t !== null; ) { + switch (t.tag) { + case 24: + case 3: + var n = Lt(); + e = Kn(n); + var a = Pn(t, e, n); + a !== null && (zt(a, t, n), Wa(a, t, n)), t = { cache: Mu() }, e.payload = t; + return; + } + t = t.return; + } + } + function L0(e, t, n) { + var a = Lt(); + n = { + lane: a, + revertLane: 0, + gesture: null, + action: n, + hasEagerState: !1, + eagerState: null, + next: null + }, zr(e) ? Zd(t, n) : (n = gu(e, t, n, a), n !== null && (zt(n, e, a), Vd(n, t, a))); + } + function Gd(e, t, n) { + var a = Lt(); + ni(e, t, n, a); + } + function ni(e, t, n, a) { + var r = { + lane: a, + revertLane: 0, + gesture: null, + action: n, + hasEagerState: !1, + eagerState: null, + next: null + }; + if (zr(e)) Zd(t, r); + else { + var u = e.alternate; + if (e.lanes === 0 && (u === null || u.lanes === 0) && (u = t.lastRenderedReducer, u !== null)) + try { + var f = t.lastRenderedState, h = u(f, n); + if (r.hasEagerState = !0, r.eagerState = h, qt(h, f)) + return rr(e, t, r, 0), Ne === null && ir(), !1; + } catch { + } + if (n = gu(e, t, r, a), n !== null) + return zt(n, e, a), Vd(n, t, a), !0; + } + return !1; + } + function ec(e, t, n, a) { + if (a = { + lane: 2, + revertLane: Nc(), + gesture: null, + action: a, + hasEagerState: !1, + eagerState: null, + next: null + }, zr(e)) { + if (t) throw Error(c(479)); + } else + t = gu( + e, + n, + a, + 2 + ), t !== null && zt(t, e, 2); + } + function zr(e) { + var t = e.alternate; + return e === re || t !== null && t === re; + } + function Zd(e, t) { + ra = Sr = !0; + var n = e.pending; + n === null ? t.next = t : (t.next = n.next, n.next = t), e.pending = t; + } + function Vd(e, t, n) { + if ((n & 4194048) !== 0) { + var a = t.lanes; + a &= e.pendingLanes, n |= a, t.lanes = n, Ff(e, n); + } + } + var li = { + readContext: ft, + use: wr, + useCallback: Ye, + useContext: Ye, + useEffect: Ye, + useImperativeHandle: Ye, + useLayoutEffect: Ye, + useInsertionEffect: Ye, + useMemo: Ye, + useReducer: Ye, + useRef: Ye, + useState: Ye, + useDebugValue: Ye, + useDeferredValue: Ye, + useTransition: Ye, + useSyncExternalStore: Ye, + useId: Ye, + useHostTransitionStatus: Ye, + useFormState: Ye, + useActionState: Ye, + useOptimistic: Ye, + useMemoCache: Ye, + useCacheRefresh: Ye + }; + li.useEffectEvent = Ye; + var Kd = { + readContext: ft, + use: wr, + useCallback: function(e, t) { + return pt().memoizedState = [ + e, + t === void 0 ? null : t + ], e; + }, + useContext: ft, + useEffect: Td, + useImperativeHandle: function(e, t, n) { + n = n != null ? n.concat([e]) : null, Or( + 4194308, + 4, + qd.bind(null, t, e), + n + ); + }, + useLayoutEffect: function(e, t) { + return Or(4194308, 4, e, t); + }, + useInsertionEffect: function(e, t) { + Or(4, 2, e, t); + }, + useMemo: function(e, t) { + var n = pt(); + t = t === void 0 ? null : t; + var a = e(); + if (Tl) { + Cn(!0); + try { + e(); + } finally { + Cn(!1); + } + } + return n.memoizedState = [a, t], a; + }, + useReducer: function(e, t, n) { + var a = pt(); + if (n !== void 0) { + var r = n(t); + if (Tl) { + Cn(!0); + try { + n(t); + } finally { + Cn(!1); + } + } + } else r = t; + return a.memoizedState = a.baseState = r, e = { + pending: null, + lanes: 0, + dispatch: null, + lastRenderedReducer: e, + lastRenderedState: r + }, a.queue = e, e = e.dispatch = L0.bind( + null, + re, + e + ), [a.memoizedState, e]; + }, + useRef: function(e) { + var t = pt(); + return e = { current: e }, t.memoizedState = e; + }, + useState: function(e) { + e = Pu(e); + var t = e.queue, n = Gd.bind(null, re, t); + return t.dispatch = n, [e.memoizedState, n]; + }, + useDebugValue: $u, + useDeferredValue: function(e, t) { + var n = pt(); + return Wu(n, e, t); + }, + useTransition: function() { + var e = Pu(!1); + return e = Cd.bind( + null, + re, + e.queue, + !0, + !1 + ), pt().memoizedState = e, [!1, e]; + }, + useSyncExternalStore: function(e, t, n) { + var a = re, r = pt(); + if (ve) { + if (n === void 0) + throw Error(c(407)); + n = n(); + } else { + if (n = t(), Ne === null) + throw Error(c(349)); + (pe & 127) !== 0 || hd(a, t, n); + } + r.memoizedState = n; + var u = { value: n, getSnapshot: t }; + return r.queue = u, Td(pd.bind(null, a, u, e), [ + e + ]), a.flags |= 2048, ua( + 9, + { destroy: void 0 }, + md.bind( + null, + a, + u, + n, + t + ), + null + ), n; + }, + useId: function() { + var e = pt(), t = Ne.identifierPrefix; + if (ve) { + var n = fn, a = cn; + n = (a & ~(1 << 32 - Nt(a) - 1)).toString(32) + n, t = "_" + t + "R_" + n, n = br++, 0 < n && (t += "H" + n.toString(32)), t += "_"; + } else + n = N0++, t = "_" + t + "r_" + n.toString(32) + "_"; + return e.memoizedState = t; + }, + useHostTransitionStatus: Iu, + useFormState: Od, + useActionState: Od, + useOptimistic: function(e) { + var t = pt(); + t.memoizedState = t.baseState = e; + var n = { + pending: null, + lanes: 0, + dispatch: null, + lastRenderedReducer: null, + lastRenderedState: null + }; + return t.queue = n, t = ec.bind( + null, + re, + !0, + n + ), n.dispatch = t, [e, t]; + }, + useMemoCache: Zu, + useCacheRefresh: function() { + return pt().memoizedState = C0.bind( + null, + re + ); + }, + useEffectEvent: function(e) { + var t = pt(), n = { impl: e }; + return t.memoizedState = n, function() { + if ((Re & 2) !== 0) + throw Error(c(440)); + return n.impl.apply(void 0, arguments); + }; + } + }, tc = { + readContext: ft, + use: wr, + useCallback: Hd, + useContext: ft, + useEffect: Fu, + useImperativeHandle: jd, + useInsertionEffect: xd, + useLayoutEffect: Nd, + useMemo: Bd, + useReducer: Ar, + useRef: Md, + useState: function() { + return Ar(zn); + }, + useDebugValue: $u, + useDeferredValue: function(e, t) { + var n = Ke(); + return _d( + n, + Me.memoizedState, + e, + t + ); + }, + useTransition: function() { + var e = Ar(zn)[0], t = Ke().memoizedState; + return [ + typeof e == "boolean" ? e : ti(e), + t + ]; + }, + useSyncExternalStore: dd, + useId: Yd, + useHostTransitionStatus: Iu, + useFormState: Rd, + useActionState: Rd, + useOptimistic: function(e, t) { + var n = Ke(); + return vd(n, Me, e, t); + }, + useMemoCache: Zu, + useCacheRefresh: Qd + }; + tc.useEffectEvent = Ud; + var Pd = { + readContext: ft, + use: wr, + useCallback: Hd, + useContext: ft, + useEffect: Fu, + useImperativeHandle: jd, + useInsertionEffect: xd, + useLayoutEffect: Nd, + useMemo: Bd, + useReducer: Ku, + useRef: Md, + useState: function() { + return Ku(zn); + }, + useDebugValue: $u, + useDeferredValue: function(e, t) { + var n = Ke(); + return Me === null ? Wu(n, e, t) : _d( + n, + Me.memoizedState, + e, + t + ); + }, + useTransition: function() { + var e = Ku(zn)[0], t = Ke().memoizedState; + return [ + typeof e == "boolean" ? e : ti(e), + t + ]; + }, + useSyncExternalStore: dd, + useId: Yd, + useHostTransitionStatus: Iu, + useFormState: Dd, + useActionState: Dd, + useOptimistic: function(e, t) { + var n = Ke(); + return Me !== null ? vd(n, Me, e, t) : (n.baseState = e, [e, n.queue.dispatch]); + }, + useMemoCache: Zu, + useCacheRefresh: Qd + }; + Pd.useEffectEvent = Ud; + function nc(e, t, n, a) { + t = e.memoizedState, n = n(a, t), n = n == null ? t : w({}, t, n), e.memoizedState = n, e.lanes === 0 && (e.updateQueue.baseState = n); + } + var lc = { + enqueueSetState: function(e, t, n) { + e = e._reactInternals; + var a = Lt(), r = Kn(a); + r.payload = t, n != null && (r.callback = n), t = Pn(e, r, a), t !== null && (zt(t, e, a), Wa(t, e, a)); + }, + enqueueReplaceState: function(e, t, n) { + e = e._reactInternals; + var a = Lt(), r = Kn(a); + r.tag = 1, r.payload = t, n != null && (r.callback = n), t = Pn(e, r, a), t !== null && (zt(t, e, a), Wa(t, e, a)); + }, + enqueueForceUpdate: function(e, t) { + e = e._reactInternals; + var n = Lt(), a = Kn(n); + a.tag = 2, t != null && (a.callback = t), t = Pn(e, a, n), t !== null && (zt(t, e, n), Wa(t, e, n)); + } + }; + function Jd(e, t, n, a, r, u, f) { + return e = e.stateNode, typeof e.shouldComponentUpdate == "function" ? e.shouldComponentUpdate(a, u, f) : t.prototype && t.prototype.isPureReactComponent ? !Ga(n, a) || !Ga(r, u) : !0; + } + function Fd(e, t, n, a) { + e = t.state, typeof t.componentWillReceiveProps == "function" && t.componentWillReceiveProps(n, a), typeof t.UNSAFE_componentWillReceiveProps == "function" && t.UNSAFE_componentWillReceiveProps(n, a), t.state !== e && lc.enqueueReplaceState(t, t.state, null); + } + function Ul(e, t) { + var n = t; + if ("ref" in t) { n = {}; - for (var o in t) - o !== "key" && (n[o] = t[o]); - } else n = t; - return t = n.ref, { - $$typeof: r, - type: a, - key: l, - ref: t !== void 0 ? t : null, - props: n - }; - } - return d.Fragment = i, d.jsx = c, d.jsxs = c, d; -} -var j; -function I() { - return j || (j = 1, x.exports = R()), x.exports; -} -var e = I(); -const u = window.React.useState, T = window.UIComponents.Button, U = window.UIComponents.Select, k = window.UIComponents.SelectContent, f = window.UIComponents.SelectItem, N = window.UIComponents.SelectTrigger, b = window.UIComponents.SelectValue, E = window.axios, L = window.UIComponents.Textarea, p = window.UIComponents.Label, _ = window.UIComponents.Checkbox, A = window.UIComponents.Toaster, v = window.sonner.toast, F = window.LucideReact; -function J({ data: r }) { - var h; - const [i, c] = u(""), [a, t] = u(""), [n, l] = u(!1), [o, C] = u(!1), S = async (s) => { - s.preventDefault(), l(!0); - try { - const m = { - message: a, - ...o ? { sendToAll: !0 } : { userId: i } - }, g = await E.post("", m); - console.log(g.data), v.success("Сообщение успешно отправлено!"); - } catch (m) { - console.error(m), v.error("Ошибка при отправке сообщения."); + for (var a in t) + a !== "ref" && (n[a] = t[a]); + } + if (e = e.defaultProps) { + n === t && (n = w({}, n)); + for (var r in e) + n[r] === void 0 && (n[r] = e[r]); + } + return n; + } + function $d(e) { + ar(e); + } + function Wd(e) { + console.error(e); + } + function kd(e) { + ar(e); + } + function Dr(e, t) { + try { + var n = e.onUncaughtError; + n(t.value, { componentStack: t.stack }); + } catch (a) { + setTimeout(function() { + throw a; + }); + } + } + function Id(e, t, n) { + try { + var a = e.onCaughtError; + a(n.value, { + componentStack: n.stack, + errorBoundary: t.tag === 1 ? t.stateNode : null + }); + } catch (r) { + setTimeout(function() { + throw r; + }); + } + } + function ac(e, t, n) { + return n = Kn(n), n.tag = 3, n.payload = { element: null }, n.callback = function() { + Dr(e, t); + }, n; + } + function eh(e) { + return e = Kn(e), e.tag = 3, e; + } + function th(e, t, n, a) { + var r = n.type.getDerivedStateFromError; + if (typeof r == "function") { + var u = a.value; + e.payload = function() { + return r(u); + }, e.callback = function() { + Id(t, n, a); + }; + } + var f = n.stateNode; + f !== null && typeof f.componentDidCatch == "function" && (e.callback = function() { + Id(t, n, a), typeof r != "function" && (In === null ? In = /* @__PURE__ */ new Set([this]) : In.add(this)); + var h = a.stack; + this.componentDidCatch(a.value, { + componentStack: h !== null ? h : "" + }); + }); + } + function X0(e, t, n, a, r) { + if (n.flags |= 32768, a !== null && typeof a == "object" && typeof a.then == "function") { + if (t = n.alternate, t !== null && ea( + t, + n, + r, + !0 + ), n = Ht.current, n !== null) { + switch (n.tag) { + case 31: + case 13: + return $t === null ? Lr() : n.alternate === null && Qe === 0 && (Qe = 3), n.flags &= -257, n.flags |= 65536, n.lanes = r, a === mr ? n.flags |= 16384 : (t = n.updateQueue, t === null ? n.updateQueue = /* @__PURE__ */ new Set([a]) : t.add(a), Tc(e, a, r)), !1; + case 22: + return n.flags |= 65536, a === mr ? n.flags |= 16384 : (t = n.updateQueue, t === null ? (t = { + transitions: null, + markerInstances: null, + retryQueue: /* @__PURE__ */ new Set([a]) + }, n.updateQueue = t) : (n = t.retryQueue, n === null ? t.retryQueue = /* @__PURE__ */ new Set([a]) : n.add(a)), Tc(e, a, r)), !1; + } + throw Error(c(435, n.tag)); + } + return Tc(e, a, r), Lr(), !1; + } + if (ve) + return t = Ht.current, t !== null ? ((t.flags & 65536) === 0 && (t.flags |= 256), t.flags |= 65536, t.lanes = r, a !== Au && (e = Error(c(422), { cause: a }), Ka(Kt(e, n)))) : (a !== Au && (t = Error(c(423), { + cause: a + }), Ka( + Kt(t, n) + )), e = e.current.alternate, e.flags |= 65536, r &= -r, e.lanes |= r, a = Kt(a, n), r = ac( + e.stateNode, + a, + r + ), ju(e, r), Qe !== 4 && (Qe = 2)), !1; + var u = Error(c(520), { cause: a }); + if (u = Kt(u, n), oi === null ? oi = [u] : oi.push(u), Qe !== 4 && (Qe = 2), t === null) return !0; + a = Kt(a, n), n = t; + do { + switch (n.tag) { + case 3: + return n.flags |= 65536, e = r & -r, n.lanes |= e, e = ac(n.stateNode, a, e), ju(n, e), !1; + case 1: + if (t = n.type, u = n.stateNode, (n.flags & 128) === 0 && (typeof t.getDerivedStateFromError == "function" || u !== null && typeof u.componentDidCatch == "function" && (In === null || !In.has(u)))) + return n.flags |= 65536, r &= -r, n.lanes |= r, r = eh(r), th( + r, + e, + n, + a + ), ju(n, r), !1; + } + n = n.return; + } while (n !== null); + return !1; + } + var ic = Error(c(461)), ke = !1; + function ot(e, t, n, a) { + t.child = e === null ? id(t, null, n, a) : Ml( + t, + e.child, + n, + a + ); + } + function nh(e, t, n, a, r) { + n = n.render; + var u = t.ref; + if ("ref" in a) { + var f = {}; + for (var h in a) + h !== "ref" && (f[h] = a[h]); + } else f = a; + return Ol(t), a = Xu( + e, + t, + n, + f, + u, + r + ), h = Yu(), e !== null && !ke ? (Qu(e, t, r), Dn(e, t, r)) : (ve && h && Eu(t), t.flags |= 1, ot(e, t, a, r), t.child); + } + function lh(e, t, n, a, r) { + if (e === null) { + var u = n.type; + return typeof u == "function" && !vu(u) && u.defaultProps === void 0 && n.compare === null ? (t.tag = 15, t.type = u, ah( + e, + t, + u, + a, + r + )) : (e = ur( + n.type, + null, + a, + t, + t.mode, + r + ), e.ref = t.ref, e.return = t, t.child = e); + } + if (u = e.child, !hc(e, r)) { + var f = u.memoizedProps; + if (n = n.compare, n = n !== null ? n : Ga, n(f, a) && e.ref === t.ref) + return Dn(e, t, r); + } + return t.flags |= 1, e = En(u, a), e.ref = t.ref, e.return = t, t.child = e; + } + function ah(e, t, n, a, r) { + if (e !== null) { + var u = e.memoizedProps; + if (Ga(u, a) && e.ref === t.ref) + if (ke = !1, t.pendingProps = a = u, hc(e, r)) + (e.flags & 131072) !== 0 && (ke = !0); + else + return t.lanes = e.lanes, Dn(e, t, r); + } + return rc( + e, + t, + n, + a, + r + ); + } + function ih(e, t, n, a) { + var r = a.children, u = e !== null ? e.memoizedState : null; + if (e === null && t.stateNode === null && (t.stateNode = { + _visibility: 1, + _pendingMarkers: null, + _retryCache: null, + _transitions: null + }), a.mode === "hidden") { + if ((t.flags & 128) !== 0) { + if (u = u !== null ? u.baseLanes | n : n, e !== null) { + for (a = t.child = e.child, r = 0; a !== null; ) + r = r | a.lanes | a.childLanes, a = a.sibling; + a = r & ~u; + } else a = 0, t.child = null; + return rh( + e, + t, + u, + n, + a + ); + } + if ((n & 536870912) !== 0) + t.memoizedState = { baseLanes: 0, cachePool: null }, e !== null && dr( + t, + u !== null ? u.cachePool : null + ), u !== null ? ud(t, u) : Bu(), cd(t); + else + return a = t.lanes = 536870912, rh( + e, + t, + u !== null ? u.baseLanes | n : n, + n, + a + ); + } else + u !== null ? (dr(t, u.cachePool), ud(t, u), Fn(), t.memoizedState = null) : (e !== null && dr(t, null), Bu(), Fn()); + return ot(e, t, r, n), t.child; + } + function ai(e, t) { + return e !== null && e.tag === 22 || t.stateNode !== null || (t.stateNode = { + _visibility: 1, + _pendingMarkers: null, + _retryCache: null, + _transitions: null + }), t.sibling; + } + function rh(e, t, n, a, r) { + var u = Uu(); + return u = u === null ? null : { parent: $e._currentValue, pool: u }, t.memoizedState = { + baseLanes: n, + cachePool: u + }, e !== null && dr(t, null), Bu(), cd(t), e !== null && ea(e, t, a, !0), t.childLanes = r, null; + } + function Mr(e, t) { + return t = Ur( + { mode: t.mode, children: t.children }, + e.mode + ), t.ref = e.ref, e.child = t, t.return = e, t; + } + function sh(e, t, n) { + return Ml(t, e.child, null, n), e = Mr(t, t.pendingProps), e.flags |= 2, Bt(t), t.memoizedState = null, e; + } + function Y0(e, t, n) { + var a = t.pendingProps, r = (t.flags & 128) !== 0; + if (t.flags &= -129, e === null) { + if (ve) { + if (a.mode === "hidden") + return e = Mr(t, a), t.lanes = 536870912, ai(null, e); + if (Cu(t), (e = je) ? (e = Sm( + e, + Ft + ), e = e !== null && e.data === "&" ? e : null, e !== null && (t.memoizedState = { + dehydrated: e, + treeContext: Yn !== null ? { id: cn, overflow: fn } : null, + retryLane: 536870912, + hydrationErrors: null + }, n = Zo(e), n.return = t, t.child = n, ct = t, je = null)) : e = null, e === null) throw Gn(t); + return t.lanes = 536870912, null; + } + return Mr(t, a); + } + var u = e.memoizedState; + if (u !== null) { + var f = u.dehydrated; + if (Cu(t), r) + if (t.flags & 256) + t.flags &= -257, t = sh( + e, + t, + n + ); + else if (t.memoizedState !== null) + t.child = e.child, t.flags |= 128, t = null; + else throw Error(c(558)); + else if (ke || ea(e, t, n, !1), r = (n & e.childLanes) !== 0, ke || r) { + if (a = Ne, a !== null && (f = $f(a, n), f !== 0 && f !== u.retryLane)) + throw u.retryLane = f, bl(e, f), zt(a, e, f), ic; + Lr(), t = sh( + e, + t, + n + ); + } else + e = u.treeContext, je = Wt(f.nextSibling), ct = t, ve = !0, Qn = null, Ft = !1, e !== null && Po(t, e), t = Mr(t, a), t.flags |= 4096; + return t; + } + return e = En(e.child, { + mode: a.mode, + children: a.children + }), e.ref = t.ref, t.child = e, e.return = t, e; + } + function Tr(e, t) { + var n = t.ref; + if (n === null) + e !== null && e.ref !== null && (t.flags |= 4194816); + else { + if (typeof n != "function" && typeof n != "object") + throw Error(c(284)); + (e === null || e.ref !== n) && (t.flags |= 4194816); + } + } + function rc(e, t, n, a, r) { + return Ol(t), n = Xu( + e, + t, + n, + a, + void 0, + r + ), a = Yu(), e !== null && !ke ? (Qu(e, t, r), Dn(e, t, r)) : (ve && a && Eu(t), t.flags |= 1, ot(e, t, n, r), t.child); + } + function uh(e, t, n, a, r, u) { + return Ol(t), t.updateQueue = null, n = od( + t, + a, + n, + r + ), fd(e), a = Yu(), e !== null && !ke ? (Qu(e, t, u), Dn(e, t, u)) : (ve && a && Eu(t), t.flags |= 1, ot(e, t, n, u), t.child); + } + function ch(e, t, n, a, r) { + if (Ol(t), t.stateNode === null) { + var u = $l, f = n.contextType; + typeof f == "object" && f !== null && (u = ft(f)), u = new n(a, u), t.memoizedState = u.state !== null && u.state !== void 0 ? u.state : null, u.updater = lc, t.stateNode = u, u._reactInternals = t, u = t.stateNode, u.props = a, u.state = t.memoizedState, u.refs = {}, Nu(t), f = n.contextType, u.context = typeof f == "object" && f !== null ? ft(f) : $l, u.state = t.memoizedState, f = n.getDerivedStateFromProps, typeof f == "function" && (nc( + t, + n, + f, + a + ), u.state = t.memoizedState), typeof n.getDerivedStateFromProps == "function" || typeof u.getSnapshotBeforeUpdate == "function" || typeof u.UNSAFE_componentWillMount != "function" && typeof u.componentWillMount != "function" || (f = u.state, typeof u.componentWillMount == "function" && u.componentWillMount(), typeof u.UNSAFE_componentWillMount == "function" && u.UNSAFE_componentWillMount(), f !== u.state && lc.enqueueReplaceState(u, u.state, null), Ia(t, a, u, r), ka(), u.state = t.memoizedState), typeof u.componentDidMount == "function" && (t.flags |= 4194308), a = !0; + } else if (e === null) { + u = t.stateNode; + var h = t.memoizedProps, y = Ul(n, h); + u.props = y; + var R = u.context, M = n.contextType; + f = $l, typeof M == "object" && M !== null && (f = ft(M)); + var U = n.getDerivedStateFromProps; + M = typeof U == "function" || typeof u.getSnapshotBeforeUpdate == "function", h = t.pendingProps !== h, M || typeof u.UNSAFE_componentWillReceiveProps != "function" && typeof u.componentWillReceiveProps != "function" || (h || R !== f) && Fd( + t, + u, + a, + f + ), Vn = !1; + var z = t.memoizedState; + u.state = z, Ia(t, a, u, r), ka(), R = t.memoizedState, h || z !== R || Vn ? (typeof U == "function" && (nc( + t, + n, + U, + a + ), R = t.memoizedState), (y = Vn || Jd( + t, + n, + y, + a, + z, + R, + f + )) ? (M || typeof u.UNSAFE_componentWillMount != "function" && typeof u.componentWillMount != "function" || (typeof u.componentWillMount == "function" && u.componentWillMount(), typeof u.UNSAFE_componentWillMount == "function" && u.UNSAFE_componentWillMount()), typeof u.componentDidMount == "function" && (t.flags |= 4194308)) : (typeof u.componentDidMount == "function" && (t.flags |= 4194308), t.memoizedProps = a, t.memoizedState = R), u.props = a, u.state = R, u.context = f, a = y) : (typeof u.componentDidMount == "function" && (t.flags |= 4194308), a = !1); + } else { + u = t.stateNode, qu(e, t), f = t.memoizedProps, M = Ul(n, f), u.props = M, U = t.pendingProps, z = u.context, R = n.contextType, y = $l, typeof R == "object" && R !== null && (y = ft(R)), h = n.getDerivedStateFromProps, (R = typeof h == "function" || typeof u.getSnapshotBeforeUpdate == "function") || typeof u.UNSAFE_componentWillReceiveProps != "function" && typeof u.componentWillReceiveProps != "function" || (f !== U || z !== y) && Fd( + t, + u, + a, + y + ), Vn = !1, z = t.memoizedState, u.state = z, Ia(t, a, u, r), ka(); + var D = t.memoizedState; + f !== U || z !== D || Vn || e !== null && e.dependencies !== null && fr(e.dependencies) ? (typeof h == "function" && (nc( + t, + n, + h, + a + ), D = t.memoizedState), (M = Vn || Jd( + t, + n, + M, + a, + z, + D, + y + ) || e !== null && e.dependencies !== null && fr(e.dependencies)) ? (R || typeof u.UNSAFE_componentWillUpdate != "function" && typeof u.componentWillUpdate != "function" || (typeof u.componentWillUpdate == "function" && u.componentWillUpdate(a, D, y), typeof u.UNSAFE_componentWillUpdate == "function" && u.UNSAFE_componentWillUpdate( + a, + D, + y + )), typeof u.componentDidUpdate == "function" && (t.flags |= 4), typeof u.getSnapshotBeforeUpdate == "function" && (t.flags |= 1024)) : (typeof u.componentDidUpdate != "function" || f === e.memoizedProps && z === e.memoizedState || (t.flags |= 4), typeof u.getSnapshotBeforeUpdate != "function" || f === e.memoizedProps && z === e.memoizedState || (t.flags |= 1024), t.memoizedProps = a, t.memoizedState = D), u.props = a, u.state = D, u.context = y, a = M) : (typeof u.componentDidUpdate != "function" || f === e.memoizedProps && z === e.memoizedState || (t.flags |= 4), typeof u.getSnapshotBeforeUpdate != "function" || f === e.memoizedProps && z === e.memoizedState || (t.flags |= 1024), a = !1); + } + return u = a, Tr(e, t), a = (t.flags & 128) !== 0, u || a ? (u = t.stateNode, n = a && typeof n.getDerivedStateFromError != "function" ? null : u.render(), t.flags |= 1, e !== null && a ? (t.child = Ml( + t, + e.child, + null, + r + ), t.child = Ml( + t, + null, + n, + r + )) : ot(e, t, n, r), t.memoizedState = u.state, e = t.child) : e = Dn( + e, + t, + r + ), e; + } + function fh(e, t, n, a) { + return wl(), t.flags |= 256, ot(e, t, n, a), t.child; + } + var sc = { + dehydrated: null, + treeContext: null, + retryLane: 0, + hydrationErrors: null + }; + function uc(e) { + return { baseLanes: e, cachePool: Io() }; + } + function cc(e, t, n) { + return e = e !== null ? e.childLanes & ~n : 0, t && (e |= Ct), e; + } + function oh(e, t, n) { + var a = t.pendingProps, r = !1, u = (t.flags & 128) !== 0, f; + if ((f = u) || (f = e !== null && e.memoizedState === null ? !1 : (Ve.current & 2) !== 0), f && (r = !0, t.flags &= -129), f = (t.flags & 32) !== 0, t.flags &= -33, e === null) { + if (ve) { + if (r ? Jn(t) : Fn(), (e = je) ? (e = Sm( + e, + Ft + ), e = e !== null && e.data !== "&" ? e : null, e !== null && (t.memoizedState = { + dehydrated: e, + treeContext: Yn !== null ? { id: cn, overflow: fn } : null, + retryLane: 536870912, + hydrationErrors: null + }, n = Zo(e), n.return = t, t.child = n, ct = t, je = null)) : e = null, e === null) throw Gn(t); + return Vc(e) ? t.lanes = 32 : t.lanes = 536870912, null; + } + var h = a.children; + return a = a.fallback, r ? (Fn(), r = t.mode, h = Ur( + { mode: "hidden", children: h }, + r + ), a = El( + a, + r, + n, + null + ), h.return = t, a.return = t, h.sibling = a, t.child = h, a = t.child, a.memoizedState = uc(n), a.childLanes = cc( + e, + f, + n + ), t.memoizedState = sc, ai(null, a)) : (Jn(t), fc(t, h)); + } + var y = e.memoizedState; + if (y !== null && (h = y.dehydrated, h !== null)) { + if (u) + t.flags & 256 ? (Jn(t), t.flags &= -257, t = oc( + e, + t, + n + )) : t.memoizedState !== null ? (Fn(), t.child = e.child, t.flags |= 128, t = null) : (Fn(), h = a.fallback, r = t.mode, a = Ur( + { mode: "visible", children: a.children }, + r + ), h = El( + h, + r, + n, + null + ), h.flags |= 2, a.return = t, h.return = t, a.sibling = h, t.child = a, Ml( + t, + e.child, + null, + n + ), a = t.child, a.memoizedState = uc(n), a.childLanes = cc( + e, + f, + n + ), t.memoizedState = sc, t = ai(null, a)); + else if (Jn(t), Vc(h)) { + if (f = h.nextSibling && h.nextSibling.dataset, f) var R = f.dgst; + f = R, a = Error(c(419)), a.stack = "", a.digest = f, Ka({ value: a, source: null, stack: null }), t = oc( + e, + t, + n + ); + } else if (ke || ea(e, t, n, !1), f = (n & e.childLanes) !== 0, ke || f) { + if (f = Ne, f !== null && (a = $f(f, n), a !== 0 && a !== y.retryLane)) + throw y.retryLane = a, bl(e, a), zt(f, e, a), ic; + Zc(h) || Lr(), t = oc( + e, + t, + n + ); + } else + Zc(h) ? (t.flags |= 192, t.child = e.child, t = null) : (e = y.treeContext, je = Wt( + h.nextSibling + ), ct = t, ve = !0, Qn = null, Ft = !1, e !== null && Po(t, e), t = fc( + t, + a.children + ), t.flags |= 4096); + return t; + } + return r ? (Fn(), h = a.fallback, r = t.mode, y = e.child, R = y.sibling, a = En(y, { + mode: "hidden", + children: a.children + }), a.subtreeFlags = y.subtreeFlags & 65011712, R !== null ? h = En( + R, + h + ) : (h = El( + h, + r, + n, + null + ), h.flags |= 2), h.return = t, a.return = t, a.sibling = h, t.child = a, ai(null, a), a = t.child, h = e.child.memoizedState, h === null ? h = uc(n) : (r = h.cachePool, r !== null ? (y = $e._currentValue, r = r.parent !== y ? { parent: y, pool: y } : r) : r = Io(), h = { + baseLanes: h.baseLanes | n, + cachePool: r + }), a.memoizedState = h, a.childLanes = cc( + e, + f, + n + ), t.memoizedState = sc, ai(e.child, a)) : (Jn(t), n = e.child, e = n.sibling, n = En(n, { + mode: "visible", + children: a.children + }), n.return = t, n.sibling = null, e !== null && (f = t.deletions, f === null ? (t.deletions = [e], t.flags |= 16) : f.push(e)), t.child = n, t.memoizedState = null, n); + } + function fc(e, t) { + return t = Ur( + { mode: "visible", children: t }, + e.mode + ), t.return = e, e.child = t; + } + function Ur(e, t) { + return e = jt(22, e, null, t), e.lanes = 0, e; + } + function oc(e, t, n) { + return Ml(t, e.child, null, n), e = fc( + t, + t.pendingProps.children + ), e.flags |= 2, t.memoizedState = null, e; + } + function dh(e, t, n) { + e.lanes |= t; + var a = e.alternate; + a !== null && (a.lanes |= t), zu(e.return, t, n); + } + function dc(e, t, n, a, r, u) { + var f = e.memoizedState; + f === null ? e.memoizedState = { + isBackwards: t, + rendering: null, + renderingStartTime: 0, + last: a, + tail: n, + tailMode: r, + treeForkCount: u + } : (f.isBackwards = t, f.rendering = null, f.renderingStartTime = 0, f.last = a, f.tail = n, f.tailMode = r, f.treeForkCount = u); + } + function hh(e, t, n) { + var a = t.pendingProps, r = a.revealOrder, u = a.tail; + a = a.children; + var f = Ve.current, h = (f & 2) !== 0; + if (h ? (f = f & 1 | 2, t.flags |= 128) : f &= 1, ee(Ve, f), ot(e, t, a, n), a = ve ? Va : 0, !h && e !== null && (e.flags & 128) !== 0) + e: for (e = t.child; e !== null; ) { + if (e.tag === 13) + e.memoizedState !== null && dh(e, n, t); + else if (e.tag === 19) + dh(e, n, t); + else if (e.child !== null) { + e.child.return = e, e = e.child; + continue; + } + if (e === t) break e; + for (; e.sibling === null; ) { + if (e.return === null || e.return === t) + break e; + e = e.return; + } + e.sibling.return = e.return, e = e.sibling; + } + switch (r) { + case "forwards": + for (n = t.child, r = null; n !== null; ) + e = n.alternate, e !== null && vr(e) === null && (r = n), n = n.sibling; + n = r, n === null ? (r = t.child, t.child = null) : (r = n.sibling, n.sibling = null), dc( + t, + !1, + r, + n, + u, + a + ); + break; + case "backwards": + case "unstable_legacy-backwards": + for (n = null, r = t.child, t.child = null; r !== null; ) { + if (e = r.alternate, e !== null && vr(e) === null) { + t.child = r; + break; + } + e = r.sibling, r.sibling = n, n = r, r = e; + } + dc( + t, + !0, + n, + null, + u, + a + ); + break; + case "together": + dc( + t, + !1, + null, + null, + void 0, + a + ); + break; + default: + t.memoizedState = null; + } + return t.child; + } + function Dn(e, t, n) { + if (e !== null && (t.dependencies = e.dependencies), kn |= t.lanes, (n & t.childLanes) === 0) + if (e !== null) { + if (ea( + e, + t, + n, + !1 + ), (n & t.childLanes) === 0) + return null; + } else return null; + if (e !== null && t.child !== e.child) + throw Error(c(153)); + if (t.child !== null) { + for (e = t.child, n = En(e, e.pendingProps), t.child = n, n.return = t; e.sibling !== null; ) + e = e.sibling, n = n.sibling = En(e, e.pendingProps), n.return = t; + n.sibling = null; + } + return t.child; + } + function hc(e, t) { + return (e.lanes & t) !== 0 ? !0 : (e = e.dependencies, !!(e !== null && fr(e))); + } + function Q0(e, t, n) { + switch (t.tag) { + case 3: + Xe(t, t.stateNode.containerInfo), Zn(t, $e, e.memoizedState.cache), wl(); + break; + case 27: + case 5: + _e(t); + break; + case 4: + Xe(t, t.stateNode.containerInfo); + break; + case 10: + Zn( + t, + t.type, + t.memoizedProps.value + ); + break; + case 31: + if (t.memoizedState !== null) + return t.flags |= 128, Cu(t), null; + break; + case 13: + var a = t.memoizedState; + if (a !== null) + return a.dehydrated !== null ? (Jn(t), t.flags |= 128, null) : (n & t.child.childLanes) !== 0 ? oh(e, t, n) : (Jn(t), e = Dn( + e, + t, + n + ), e !== null ? e.sibling : null); + Jn(t); + break; + case 19: + var r = (e.flags & 128) !== 0; + if (a = (n & t.childLanes) !== 0, a || (ea( + e, + t, + n, + !1 + ), a = (n & t.childLanes) !== 0), r) { + if (a) + return hh( + e, + t, + n + ); + t.flags |= 128; + } + if (r = t.memoizedState, r !== null && (r.rendering = null, r.tail = null, r.lastEffect = null), ee(Ve, Ve.current), a) break; + return null; + case 22: + return t.lanes = 0, ih( + e, + t, + n, + t.pendingProps + ); + case 24: + Zn(t, $e, e.memoizedState.cache); + } + return Dn(e, t, n); + } + function mh(e, t, n) { + if (e !== null) + if (e.memoizedProps !== t.pendingProps) + ke = !0; + else { + if (!hc(e, n) && (t.flags & 128) === 0) + return ke = !1, Q0( + e, + t, + n + ); + ke = (e.flags & 131072) !== 0; + } + else + ke = !1, ve && (t.flags & 1048576) !== 0 && Ko(t, Va, t.index); + switch (t.lanes = 0, t.tag) { + case 16: + e: { + var a = t.pendingProps; + if (e = zl(t.elementType), t.type = e, typeof e == "function") + vu(e) ? (a = Ul(e, a), t.tag = 1, t = ch( + null, + t, + e, + a, + n + )) : (t.tag = 0, t = rc( + null, + t, + e, + a, + n + )); + else { + if (e != null) { + var r = e.$$typeof; + if (r === fe) { + t.tag = 11, t = nh( + null, + t, + e, + a, + n + ); + break e; + } else if (r === I) { + t.tag = 14, t = lh( + null, + t, + e, + a, + n + ); + break e; + } + } + throw t = $(e) || e, Error(c(306, t, "")); + } + } + return t; + case 0: + return rc( + e, + t, + t.type, + t.pendingProps, + n + ); + case 1: + return a = t.type, r = Ul( + a, + t.pendingProps + ), ch( + e, + t, + a, + r, + n + ); + case 3: + e: { + if (Xe( + t, + t.stateNode.containerInfo + ), e === null) throw Error(c(387)); + a = t.pendingProps; + var u = t.memoizedState; + r = u.element, qu(e, t), Ia(t, a, null, n); + var f = t.memoizedState; + if (a = f.cache, Zn(t, $e, a), a !== u.cache && Du( + t, + [$e], + n, + !0 + ), ka(), a = f.element, u.isDehydrated) + if (u = { + element: a, + isDehydrated: !1, + cache: f.cache + }, t.updateQueue.baseState = u, t.memoizedState = u, t.flags & 256) { + t = fh( + e, + t, + a, + n + ); + break e; + } else if (a !== r) { + r = Kt( + Error(c(424)), + t + ), Ka(r), t = fh( + e, + t, + a, + n + ); + break e; + } else + for (e = t.stateNode.containerInfo, e.nodeType === 9 ? e = e.body : e = e.nodeName === "HTML" ? e.ownerDocument.body : e, je = Wt(e.firstChild), ct = t, ve = !0, Qn = null, Ft = !0, n = id( + t, + null, + a, + n + ), t.child = n; n; ) + n.flags = n.flags & -3 | 4096, n = n.sibling; + else { + if (wl(), a === r) { + t = Dn( + e, + t, + n + ); + break e; + } + ot(e, t, a, n); + } + t = t.child; + } + return t; + case 26: + return Tr(e, t), e === null ? (n = Rm( + t.type, + null, + t.pendingProps, + null + )) ? t.memoizedState = n : ve || (n = t.type, e = t.pendingProps, a = Kr( + Pe.current + ).createElement(n), a[ut] = t, a[bt] = e, dt(a, n, e), at(a), t.stateNode = a) : t.memoizedState = Rm( + t.type, + e.memoizedProps, + t.pendingProps, + e.memoizedState + ), null; + case 27: + return _e(t), e === null && ve && (a = t.stateNode = wm( + t.type, + t.pendingProps, + Pe.current + ), ct = t, Ft = !0, r = je, ll(t.type) ? (Kc = r, je = Wt(a.firstChild)) : je = r), ot( + e, + t, + t.pendingProps.children, + n + ), Tr(e, t), e === null && (t.flags |= 4194304), t.child; + case 5: + return e === null && ve && ((r = a = je) && (a = vv( + a, + t.type, + t.pendingProps, + Ft + ), a !== null ? (t.stateNode = a, ct = t, je = Wt(a.firstChild), Ft = !1, r = !0) : r = !1), r || Gn(t)), _e(t), r = t.type, u = t.pendingProps, f = e !== null ? e.memoizedProps : null, a = u.children, Yc(r, u) ? a = null : f !== null && Yc(r, f) && (t.flags |= 32), t.memoizedState !== null && (r = Xu( + e, + t, + q0, + null, + null, + n + ), Si._currentValue = r), Tr(e, t), ot(e, t, a, n), t.child; + case 6: + return e === null && ve && ((e = n = je) && (n = Sv( + n, + t.pendingProps, + Ft + ), n !== null ? (t.stateNode = n, ct = t, je = null, e = !0) : e = !1), e || Gn(t)), null; + case 13: + return oh(e, t, n); + case 4: + return Xe( + t, + t.stateNode.containerInfo + ), a = t.pendingProps, e === null ? t.child = Ml( + t, + null, + a, + n + ) : ot(e, t, a, n), t.child; + case 11: + return nh( + e, + t, + t.type, + t.pendingProps, + n + ); + case 7: + return ot( + e, + t, + t.pendingProps, + n + ), t.child; + case 8: + return ot( + e, + t, + t.pendingProps.children, + n + ), t.child; + case 12: + return ot( + e, + t, + t.pendingProps.children, + n + ), t.child; + case 10: + return a = t.pendingProps, Zn(t, t.type, a.value), ot(e, t, a.children, n), t.child; + case 9: + return r = t.type._context, a = t.pendingProps.children, Ol(t), r = ft(r), a = a(r), t.flags |= 1, ot(e, t, a, n), t.child; + case 14: + return lh( + e, + t, + t.type, + t.pendingProps, + n + ); + case 15: + return ah( + e, + t, + t.type, + t.pendingProps, + n + ); + case 19: + return hh(e, t, n); + case 31: + return Y0(e, t, n); + case 22: + return ih( + e, + t, + n, + t.pendingProps + ); + case 24: + return Ol(t), a = ft($e), e === null ? (r = Uu(), r === null && (r = Ne, u = Mu(), r.pooledCache = u, u.refCount++, u !== null && (r.pooledCacheLanes |= n), r = u), t.memoizedState = { parent: a, cache: r }, Nu(t), Zn(t, $e, r)) : ((e.lanes & n) !== 0 && (qu(e, t), Ia(t, null, null, n), ka()), r = e.memoizedState, u = t.memoizedState, r.parent !== a ? (r = { parent: a, cache: a }, t.memoizedState = r, t.lanes === 0 && (t.memoizedState = t.updateQueue.baseState = r), Zn(t, $e, a)) : (a = u.cache, Zn(t, $e, a), a !== r.cache && Du( + t, + [$e], + n, + !0 + ))), ot( + e, + t, + t.pendingProps.children, + n + ), t.child; + case 29: + throw t.pendingProps; + } + throw Error(c(156, t.tag)); + } + function Mn(e) { + e.flags |= 4; + } + function mc(e, t, n, a, r) { + if ((t = (e.mode & 32) !== 0) && (t = !1), t) { + if (e.flags |= 16777216, (r & 335544128) === r) + if (e.stateNode.complete) e.flags |= 8192; + else if (Yh()) e.flags |= 8192; + else + throw Dl = mr, xu; + } else e.flags &= -16777217; + } + function ph(e, t) { + if (t.type !== "stylesheet" || (t.state.loading & 4) !== 0) + e.flags &= -16777217; + else if (e.flags |= 16777216, !Um(t)) + if (Yh()) e.flags |= 8192; + else + throw Dl = mr, xu; + } + function xr(e, t) { + t !== null && (e.flags |= 4), e.flags & 16384 && (t = e.tag !== 22 ? Pf() : 536870912, e.lanes |= t, da |= t); + } + function ii(e, t) { + if (!ve) + switch (e.tailMode) { + case "hidden": + t = e.tail; + for (var n = null; t !== null; ) + t.alternate !== null && (n = t), t = t.sibling; + n === null ? e.tail = null : n.sibling = null; + break; + case "collapsed": + n = e.tail; + for (var a = null; n !== null; ) + n.alternate !== null && (a = n), n = n.sibling; + a === null ? t || e.tail === null ? e.tail = null : e.tail.sibling = null : a.sibling = null; + } + } + function He(e) { + var t = e.alternate !== null && e.alternate.child === e.child, n = 0, a = 0; + if (t) + for (var r = e.child; r !== null; ) + n |= r.lanes | r.childLanes, a |= r.subtreeFlags & 65011712, a |= r.flags & 65011712, r.return = e, r = r.sibling; + else + for (r = e.child; r !== null; ) + n |= r.lanes | r.childLanes, a |= r.subtreeFlags, a |= r.flags, r.return = e, r = r.sibling; + return e.subtreeFlags |= a, e.childLanes = n, t; + } + function G0(e, t, n) { + var a = t.pendingProps; + switch (wu(t), t.tag) { + case 16: + case 15: + case 0: + case 11: + case 7: + case 8: + case 12: + case 9: + case 14: + return He(t), null; + case 1: + return He(t), null; + case 3: + return n = t.stateNode, a = null, e !== null && (a = e.memoizedState.cache), t.memoizedState.cache !== a && (t.flags |= 2048), On($e), Je(), n.pendingContext && (n.context = n.pendingContext, n.pendingContext = null), (e === null || e.child === null) && (Il(t) ? Mn(t) : e === null || e.memoizedState.isDehydrated && (t.flags & 256) === 0 || (t.flags |= 1024, Ou())), He(t), null; + case 26: + var r = t.type, u = t.memoizedState; + return e === null ? (Mn(t), u !== null ? (He(t), ph(t, u)) : (He(t), mc( + t, + r, + null, + a, + n + ))) : u ? u !== e.memoizedState ? (Mn(t), He(t), ph(t, u)) : (He(t), t.flags &= -16777217) : (e = e.memoizedProps, e !== a && Mn(t), He(t), mc( + t, + r, + e, + a, + n + )), null; + case 27: + if (Le(t), n = Pe.current, r = t.type, e !== null && t.stateNode != null) + e.memoizedProps !== a && Mn(t); + else { + if (!a) { + if (t.stateNode === null) + throw Error(c(166)); + return He(t), null; + } + e = ae.current, Il(t) ? Jo(t) : (e = wm(r, a, n), t.stateNode = e, Mn(t)); + } + return He(t), null; + case 5: + if (Le(t), r = t.type, e !== null && t.stateNode != null) + e.memoizedProps !== a && Mn(t); + else { + if (!a) { + if (t.stateNode === null) + throw Error(c(166)); + return He(t), null; + } + if (u = ae.current, Il(t)) + Jo(t); + else { + var f = Kr( + Pe.current + ); + switch (u) { + case 1: + u = f.createElementNS( + "http://www.w3.org/2000/svg", + r + ); + break; + case 2: + u = f.createElementNS( + "http://www.w3.org/1998/Math/MathML", + r + ); + break; + default: + switch (r) { + case "svg": + u = f.createElementNS( + "http://www.w3.org/2000/svg", + r + ); + break; + case "math": + u = f.createElementNS( + "http://www.w3.org/1998/Math/MathML", + r + ); + break; + case "script": + u = f.createElement("div"), u.innerHTML = " +
`; @@ -150,9 +155,11 @@ export function bindInertia(adminizer: Adminizer) { const defaultLocale = typeof req.adminizer.config.translation !== "boolean" ? req.adminizer.config.translation.defaultLocale : "en"; + const locale = req.user?.locale || defaultLocale; + const commonMessages = getUiTranslations(req, COMMON_UI_TRANSLATION_KEYS); req.Inertia.setViewData({ - lang: req.user?.locale || defaultLocale, + lang: locale, }) const menuHelper = new InertiaMenuHelper(adminizer) @@ -161,60 +168,9 @@ export function bindInertia(adminizer: Adminizer) { user: req.session.userPretended ?? req.user }, routePrefix: req.adminizer.config.routePrefix, - uiMessages: { - Delete: req.i18n.__("Delete"), - Diff: req.i18n.__("Diff"), - Preview: req.i18n.__("Preview"), - Add: req.i18n.__("Add"), - Search: req.i18n.__("Search"), - Yes: req.i18n.__("Yes"), - No: req.i18n.__("No"), - On: req.i18n.__("On"), - Off: req.i18n.__("Off"), - Hide: req.i18n.__("Hide"), - Show: req.i18n.__("Show"), - "No notifications found": req.i18n.__("No notifications found"), - "No widgets found": req.i18n.__("No widgets found"), - "Changes not found": req.i18n.__("Changes not found"), - Old: req.i18n.__("Old"), - New: req.i18n.__("New"), - Added: req.i18n.__("Added"), - Removed: req.i18n.__("Removed"), - Updated: req.i18n.__("Updated"), - "changed type": req.i18n.__("changed type"), - "Error: Invalid field data": req.i18n.__("Error: Invalid field data"), - "Error: Fields data is invalid": req.i18n.__("Error: Fields data is invalid"), - "No fields to display": req.i18n.__("No fields to display"), - "Error: Some fields are missing required properties (name, type, label)": req.i18n.__( - "Error: Some fields are missing required properties (name, type, label)" - ), - Edit: req.i18n.__("Edit"), - create: req.i18n.__("create"), - Clean: req.i18n.__("Clean"), - "Are you sure?": req.i18n.__("Are you sure?"), - "Select Item type": req.i18n.__("Select Item type"), - "Select Ids": req.i18n.__("Select Ids"), - "Open in a new window": req.i18n.__("Open in a new window"), - Visible: req.i18n.__("Visible"), - "Performing an action...": req.i18n.__("Performing an action..."), - "Action completed": req.i18n.__("Action completed"), - Save: req.i18n.__("Save"), - First: req.i18n.__("First"), - Last: req.i18n.__("Last"), - Previous: req.i18n.__("Previous"), - Next: req.i18n.__("Next"), - "Send feedback": req.i18n.__("Send feedback"), - "Feedback": req.i18n.__("Feedback"), - "Title": req.i18n.__("Title"), - "Description": req.i18n.__("Description"), - "Attach files": req.i18n.__("Attach files"), - "Send": req.i18n.__("Send"), - "Sending...": req.i18n.__("Sending..."), - "Feedback sent successfully": req.i18n.__("Feedback sent successfully"), - "Title is required": req.i18n.__("Title is required"), - "File size exceeds the 5 MB limit": req.i18n.__("File size exceeds the 5 MB limit"), - "Remove file": req.i18n.__("Remove file"), - "Max 5 MB per file": req.i18n.__("Max 5 MB per file"), + i18n: { + locale, + common: commonMessages, }, menu: req.user ? menuHelper.getMenuItems(req) : null, title: menuHelper.getBrandTitle(), diff --git a/src/system/bindReqFunctions.ts b/src/system/bindReqFunctions.ts index 5047f0b1..0f269533 100644 --- a/src/system/bindReqFunctions.ts +++ b/src/system/bindReqFunctions.ts @@ -19,9 +19,19 @@ export default function bindReqFunctions(adminizer: Adminizer) { typeof (req as Partial).i18n?.__ === "function"; if (!hasExternalI18n) { + const translationDirectory = + adminizer.config.translation !== false + ? adminizer.config.translation.directory ?? adminizer.config.translation.path + : null; + const missingTranslationDirectory = + adminizer.config.translation !== false + ? adminizer.config.translation.missingTranslationDirectory + : null; + req.i18n = new I18n({ locales: adminizer.config.translation !== false ? adminizer.config.translation.locales : [], - directory: adminizer.config.translation !== false ? adminizer.config.translation.path ?? null : null + directory: translationDirectory ?? null, + missingDirectory: missingTranslationDirectory ?? null }); } @@ -47,7 +57,8 @@ export default function bindReqFunctions(adminizer: Adminizer) { if (token) { const user = verifyUser(token, req.adminizer.jwtSecret); if (user) { - req.user = await req.adminizer.modelHandler.model.get('userap')['_findOne']({id: user.id}); + // Load user with groups (Sequelize adapter auto-populates associations in _findOne) + req.user = await req.adminizer.modelHandler.internal("auth").get("UserAP").findOne({where: {id: user.id}}); } } diff --git a/src/system/bindTranslations.ts b/src/system/bindTranslations.ts index b28b8e59..ff3c0d62 100644 --- a/src/system/bindTranslations.ts +++ b/src/system/bindTranslations.ts @@ -12,11 +12,18 @@ export default function bindTranslations(adminizer: Adminizer) { } return } - if (fs.existsSync(adminizer.config.translation.path)) { - let translationsDir = fs.readdirSync(adminizer.config.translation.path); + const translationsDirectory = + adminizer.config.translation.directory ?? adminizer.config.translation.path; + + if (!translationsDirectory) { + return; + } + + if (fs.existsSync(translationsDirectory)) { + let translationsDir = fs.readdirSync(translationsDirectory); if (translationsDir.length) { // load project translations - TranslationHelper.loadTranslations(adminizer, `${process.cwd()}/${adminizer.config.translation.path}`); + TranslationHelper.loadTranslations(adminizer, `${process.cwd()}/${translationsDirectory}`); } } } diff --git a/src/system/buildInternalModelAccess.ts b/src/system/buildInternalModelAccess.ts new file mode 100644 index 00000000..a43e3253 --- /dev/null +++ b/src/system/buildInternalModelAccess.ts @@ -0,0 +1,89 @@ +import { AdminpanelConfig } from "../interfaces/adminpanelConfig"; +import { InternalModelAccessMap } from "../interfaces/internalModelAccess"; +import { ModelHandler } from "../lib/model/ModelHandler"; + +export function buildInternalModelAccess( + config: AdminpanelConfig, + modelHandler: ModelHandler +): InternalModelAccessMap | undefined { + const accessMap = config.system?.internalModelAccess; + const dynamicAccessMap: InternalModelAccessMap = {}; + + addInternalAccessModels(dynamicAccessMap, "navigation", getNavigationInternalModels(config)); + addInternalAccessModels(dynamicAccessMap, "data-accessor", getDataAccessorInternalModels(config, modelHandler)); + addInternalAccessModels(dynamicAccessMap, "history", getHistoryInternalModels(config, modelHandler)); + + if (!Object.keys(dynamicAccessMap).length) { + return accessMap; + } + + return { + ...(accessMap ?? {}), + ...Object.fromEntries( + Object.entries(dynamicAccessMap).map(([scope, models]) => [ + scope, + [...(accessMap?.[scope] ?? []), ...models] + ]) + ) + }; +} + +function addInternalAccessModels(accessMap: InternalModelAccessMap, scope: string, models: string[]): void { + if (!models.length) { + return; + } + + accessMap[scope] = Array.from(new Set(models)); +} + +function getNavigationInternalModels(config: AdminpanelConfig): string[] { + if (!config.navigation) { + return []; + } + + const models = [ + config.navigation.model ?? "NavigationAP", + ...config.navigation.items + .map(item => item.model) + .filter((model): model is string => Boolean(model)) + ]; + + return Array.from(new Set(models)); +} + +function getDataAccessorInternalModels( + config: AdminpanelConfig, + modelHandler: ModelHandler +): string[] { + const models: string[] = []; + + for (const [modelName, modelConfig] of Object.entries(config.models ?? {})) { + if (!modelConfig || typeof modelConfig !== "object" || Array.isArray(modelConfig)) { + continue; + } + + const userAccessRelation = modelConfig.userAccessRelation; + if (!userAccessRelation || typeof userAccessRelation !== "object" || Array.isArray(userAccessRelation)) { + continue; + } + + const model = modelHandler.model.get(modelName); + const intermediateModelName = model?.attributes?.[userAccessRelation.field]?.model; + if (intermediateModelName) { + models.push(intermediateModelName); + } + } + + return Array.from(new Set(models)); +} + +function getHistoryInternalModels( + config: AdminpanelConfig, + modelHandler: ModelHandler +): string[] { + if (!config.history?.enabled) { + return []; + } + + return Array.from(new Set(modelHandler.model.keys())); +} diff --git a/src/system/defaults.ts b/src/system/defaults.ts index 46e8ccfd..c63f7d3b 100644 --- a/src/system/defaults.ts +++ b/src/system/defaults.ts @@ -1,6 +1,5 @@ 'use strict' import {AdminpanelConfig} from "../interfaces/adminpanelConfig"; -import {FileStorageHelper} from "../helpers/fileStorageHelper"; import timezones from "../lib/timezones"; import addUser from "../controllers/addUser.js"; import editUser from "../controllers/editUser.js"; @@ -14,6 +13,10 @@ let adminpanelConfig: AdminpanelConfig = { /** Default route prefix */ routePrefix: '/adminizer', + list: { + defaultPageSize: 50 + }, + /** * Name of model identifier field */ @@ -22,14 +25,23 @@ let adminpanelConfig: AdminpanelConfig = { /** * Policies */ - policies: [], + middlewares: [], /** * Base navbar configuration */ navbar: { // List of additional actions - additionalLinks: [] + additionalLinks: [ + { + link: '/adminizer/user-filters', + title: 'User Filters', + id: 'user-filters', + icon: 'filter_list', + section: 'System', + type: 'self' + } + ] }, brand: { @@ -110,21 +122,10 @@ let adminpanelConfig: AdminpanelConfig = { translation: { locales: ['en', 'ru'], - path: `config/locales/adminpanel`, + directory: `config/locales/adminpanel`, defaultLocale: 'en' }, - forms: { - path: `api/adminpanel-forms`, - data: {}, - get: async function (slug, key) { - return FileStorageHelper.get(slug, key) - }, - set: async function (slug, key, value) { - FileStorageHelper.set(slug, key, value) - } - }, - /** * List of sections in head */ @@ -181,6 +182,9 @@ let adminpanelConfig: AdminpanelConfig = { }, mediamanager: { fileStoragePath: '.tmp/public', + }, + filters: { + enabled: false } } diff --git a/src/system/routePolicies.ts b/src/system/routePolicies.ts new file mode 100644 index 00000000..3fccdd1b --- /dev/null +++ b/src/system/routePolicies.ts @@ -0,0 +1,41 @@ +import { Adminizer } from "../lib/Adminizer"; + +function composePolicies(action: MiddlewareType, guards: MiddlewareType[]): MiddlewareType { + return async (req, res, next) => { + try { + for (const guard of guards) { + let shouldContinue = false; + + const guardNext = ((error?: unknown) => { + if (error) { + throw error; + } + shouldContinue = true; + }) as any; + + await Promise.resolve(guard(req, res, guardNext)); + + if (!shouldContinue || res.headersSent) { + return; + } + } + + return action(req, res, next); + } catch (error) { + return next(error as any); + } + }; +} + +export function bindWithMiddlewares( + adminizer: Adminizer, + middlewares: MiddlewareType[], + action: MiddlewareType, + policies: MiddlewareType[] = [] +): MiddlewareType[] { + if (!policies.length) { + return adminizer.middlewareManager.bindMiddlewares(middlewares, action); + } + + return adminizer.middlewareManager.bindMiddlewares(middlewares, composePolicies(action, policies)); +} diff --git a/src/translations/en.json b/src/translations/en.json index b5540249..c1df61ad 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -13,6 +13,7 @@ "Create a new administrator": "Create a new administrator", "Create an Account": "Create an Account", "Custom": "Custom", + "Custom filtering": "Custom filtering", "Delete": "Delete", "E-mail": "E-mail", "Edit": "Edit", @@ -153,45 +154,156 @@ "Current": "Current", "There is no history": "There is no history", "The end of the list has been reached": "The end of the list has been reached", - "Diff": "Diff", - "Preview": "Preview", - "Add": "Add", + "Filters": "Filters", + "Saved filters": "Saved filters", + "Active filter": "Active filter", + "Add filter": "Add filter", + "Reset filter": "Reset filter", + "Equal": "equal", + "Not equal": "not equal", + "Contains": "contains", + "Starts with": "starts with", + "Ends with": "ends with", + "Is empty": "is empty", + "Is not empty": "is not empty", + "Greater than": "greater than", + "Greater or equal": "greater or equal", + "Less than": "less than", + "Less or equal": "less or equal", + "Between": "between", + "Today": "today", + "Month": "month", + "Year": "year", + "Month range": "month range", + "Year range": "year range", + "After": "after", + "After or equal": "after or equal", + "Before": "before", + "Before or equal": "before or equal", + "In list": "in list", + "Not in list": "not in list", + "Is null": "is null", + "Is not null": "is not null", + "From": "From", + "To": "To", + "From...": "From...", + "To...": "To...", + "Date...": "Date...", + "Value...": "Value...", + "Select...": "Select...", + "selected": "selected", + "Search...": "Search...", + "No results": "No results", + "Enter filter name": "Enter filter name", + "Temporary filter": "Temporary filter", + "Error: model not found": "Error: model not found", + "Error loading columns": "Error loading columns", + "Network error": "Network error", + "Columns settings": "Columns settings", + "Available columns": "Available columns", + "Visible columns": "Visible columns", + "Add all": "Add all", + "Hide all": "Hide all", + "No available columns": "No available columns", + "Move columns here": "Move columns here", + "Save changes": "Save changes", + "Apply": "Apply", + "Cancel": "Cancel", + "conditions": "conditions", + "modified": "modified", + "Private filter": "Private filter", + "For groups": "Filter for groups", + "Public filter": "Public filter", + "Only for you": "Only for you", + "For selected groups": "For selected groups", + "For all users": "For all users", + "Visible only to you": "Visible only to you", + "Visible only to your group": "Visible only to your group", + "API access (feed)": "API access (feed)", "Hide": "Hide", "Show": "Show", - "No notifications found": "No notifications found", - "Changes not found": "Changes not found", - "Old": "Old", - "New": "New", - "Added": "Added", - "Removed": "Removed", - "Updated": "Updated", - "changed type": "changed type", - "Error: Invalid field data": "Error: Invalid field data", - "Error: Fields data is invalid": "Error: Fields data is invalid", - "No fields to display": "No fields to display", - "Error: Some fields are missing required properties (name, type, label)": "Error: Some fields are missing required properties (name, type, label)", - "I'm not a robot": "I'm not a robot", - "Solving CAPTCHA...": "Solving CAPTCHA...", - "Verification complete!": "Verification complete!", - "Error solving CAPTCHA. Try again.": "Error solving CAPTCHA. Try again.", - "Invalid CAPTCHA solution": "Invalid CAPTCHA solution", - "Wrong username": "Wrong username", - "Wrong password": "Wrong password", - "Profile is not confirmed, please contact to administrator": "Profile is not confirmed, please contact to administrator", - "Profile expired, contact the administrator": "Profile expired, contact the administrator", - "The user is not allowed to enter, please contact the administrator": "The user is not allowed to enter, please contact the administrator", - "On": "On", - "Off": "Off", - "Visible": "Visible", - "The file used by the models": "The file used by the models", - "This is opensource project": "This is opensource project", - "Groups": "Groups", - "System": "System", - "W x H": "W x H", - "Sizes": "Sizes", - "First": "First", - "Last": "Last", - "Previous": "Previous", - "Next": "Next", - "Notifications": "Notifications" + "Regenerate": "Regenerate", + "copied to clipboard": "copied to clipboard", + "Failed to copy": "Failed to copy", + "Loading filters...": "Loading filters...", + "No saved filters": "No saved filters", + "Save current filter for quick access": "Save current filter for quick access", + "Someone else's private filter (owner:": "Someone else's private filter (owner:", + "unknown)": "unknown)", + "Owner": "Owner", + "Apply filter": "Apply filter", + "Delete filter": "Delete filter", + "No permission to delete filter": "No permission to delete filter", + "Edit filter": "Edit filter", + "New filter": "New filter", + "Filter conditions": "Filter conditions", + "Add condition": "Add condition", + "Select field": "Select field", + "No conditions. Add a condition above.": "No conditions. Add a condition above.", + "Description": "Description", + "Icon": "Icon", + "Color": "Color", + "Filter parameters": "Filter parameters", + "Change filter conditions and parameters": "Change filter conditions and parameters", + "Configure filter conditions": "Configure filter conditions", + "Specify name and parameters for saving": "Specify name and parameters for saving", + "Someone else's private filter": "Someone else's private filter", + "You cannot change someone else's filter visibility": "You cannot change someone else's filter visibility", + "Private": "Private", + "Public": "Public", + "Visibility": "Filter visibility", + "Export": "Export", + "Exporting...": "Exporting...", + "Export is only available with an active filter": "Export is only available with an active filter", + "Export to": "Export to", + "completed": "completed", + "Export error": "Export error", + "Conditions": "Conditions", + "Access for groups": "Access for groups", + "Selected groups": "Selected groups", + "Groups list": "Groups list", + "Columns": "Columns", + "Saving...": "Saving...", + "Error saving filter": "Error saving filter", + "Column configuration": "Column configuration", + "Select and arrange columns to display in the table": "Select and arrange columns to display in the table", + "Loading columns...": "Loading columns...", + "Use buttons": "Use buttons", + "to move and": "to move and", + "to change order": "to change order", + "Not contains": "not contains", + "Data in JSON format": "Data in JSON format", + "Text format CSV": "Text format CSV", + "Excel file": "Excel file", + "Select a saved filter or create a new one": "Select a saved filter or create a new one", + "Select a filter or create a new one": "Select a filter or create a new one", + "Filter visibility": "Filter visibility", + "Unknown": "Unknown", + "JSON URL": "JSON URL", + "XML URL": "XML URL", + "API key is not created": "API key is not created", + "The key is not created. Create it or contact an administrator.": "The key is not created. Create it or contact an administrator.", + "OK": "OK", + "Add": "Add", + "Save filter": "Save filter", + "and": "and", + "Error": "Error", + "Error applying filter": "Error applying filter", + "Model not found": "Model not found", + "Unauthorized": "Unauthorized", + "Forbidden": "Forbidden", + "Conditions are required": "Conditions are required", + "Filter name is required": "Filter name is required", + "Filter conditions are required": "Filter conditions are required", + "Filter with this name already exists": "Filter with this name already exists", + "Filter not found": "Filter not found", + "Failed to save filter": "Failed to save filter", + "Filter ID is required": "Filter ID is required", + "Failed to delete filter": "Failed to delete filter", + "Columns array is required": "Columns array is required", + "Filter not found or access denied": "Filter not found or access denied", + "Failed to update filter columns": "Failed to update filter columns", + "GroupAP model not found": "GroupAP model not found", + "Failed to load groups": "Failed to load groups", + "Internal server error": "Internal server error" } diff --git a/src/translations/ru.json b/src/translations/ru.json index 021d6e4d..518af612 100644 --- a/src/translations/ru.json +++ b/src/translations/ru.json @@ -3,7 +3,7 @@ "Actions": "Действия", "All": "Все", "Are you sure?": "Вы уверены?", - "Back": " Назад", + "Back": "Назад", "Back to Login": "Назад ко входу", "Additional login page": "Дополнительная страница входа", "Clean": "Очистить", @@ -50,7 +50,7 @@ "Record was updated": "Запись обновлена", "Register": "Регистрация", "Repeat password": "Повторить пароль", - "Reset": "Сброс", + "Reset": "Сбросить", "Save": "Сохранить", "Search": "Поиск", "Select": "Выберите", @@ -70,6 +70,55 @@ "You don't have any widgets selected yet. You can add them by clicking on the plus sign at the top right.": "У вас пока нет выбранных виджетов. Вы можете добавить их, нажав на значок плюса в правом верхнем углу.", "create": "Создать", "create new": "создать новую", + "Widget Switcher One": "Widget Switcher One", + "Site Switcher": "Site Switcher", + "Widget Switcher Two": "Widget Switcher Two", + "Switcher Two": "Switcher Two", + "Test": "Test", + "Add Test Model": "Add Test Model", + "example.com": "example.com", + "link to example.com": "link to example.com", + "Global Settings": "Global Settings", + "Info widget One": "Info widget One", + "Info One": "Info One", + "Info widget Two": "Info widget Two", + "Info Two": "Info Two", + "Info widget 3": "Info widget 3", + "Info 31111": "Info 31111", + "Info widget 4": "Info widget 4", + "Info 4": "Info 4", + "Action One": "Action One", + "Action Two": "Action Two", + "Widget Custom One": "Widget Custom One", + "Site Custom": "Site Custom", + "id": "id", + "Textarea": "Textarea", + "Disabled": "Disabled", + "Boolean": "Boolean", + "time": "time", + "Number": "Number", + "Editor": "Editor", + "Date and time": "Date and time", + "testRelationExample": "testRelationExample", + "owner": "owner", + "title": "title", + "slug": "slug", + "Images 1": "Images 1", + "Images": "Изображения", + "Videos": "Видео", + "Texts": "Текст", + "Applications": "Приложения", + "Table": "Таблица", + "Tile": "Плитка", + "Load more": "Загрузить еще", + "File": "Файл", + "Name": "Название", + "Date": "Дата", + "Type": "Тип", + "Size (orig.)": "Размер (orig.)", + "W x H (orig.)": "W x H (orig.)", + "Sizes/Ver": "Sizes/Ver", + "Locales": "Locales", "Leave this field empty if you don't want to change the password": "Оставьте это поле пустым, если не хотите менять пароль", "File type are not supported": "Этот файл не поддерживается", "History": "История", @@ -84,92 +133,190 @@ "Current": "Текущая", "There is no history": "Нет истории", "The end of the list has been reached": "Достигнут конец списка", - "Diff": "Разница", - "Preview": "Предпросмотр", - "Add": "Добавить", + "Filters": "Фильтры", + "Saved filters": "Сохранённые фильтры", + "Active filter": "Активный фильтр", + "Add filter": "Добавить фильтр", + "Reset filter": "Сбросить фильтр", + "Equal": "равно", + "Not equal": "не равно", + "Contains": "содержит", + "Starts with": "начинается с", + "Ends with": "заканчивается на", + "Is empty": "пустое", + "Is not empty": "не пустое", + "Greater than": "больше чем", + "Greater or equal": "больше или равно", + "Less than": "меньше чем", + "Less or equal": "меньше или равно", + "Between": "между", + "Today": "сегодня", + "Month": "месяц", + "Year": "год", + "Month range": "диапазон месяцев", + "Year range": "диапазон лет", + "After": "после", + "After or equal": "после или равно", + "Before": "до", + "Before or equal": "до или равно", + "In list": "в списке", + "Not in list": "не в списке", + "Is null": "пусто", + "Is not null": "не пусто", + "From": "От", + "To": "До", + "From...": "От...", + "To...": "До...", + "Date...": "Дата...", + "Value...": "Значение...", + "Select...": "Выберите...", + "selected": "выбр.", + "Search...": "Поиск...", + "No results": "Нет результатов", + "Enter filter name": "Введите название фильтра", + "Temporary filter": "Временный фильтр", + "Error: model not found": "Ошибка: модель не найдена", + "Error loading columns": "Ошибка загрузки колонок", + "Network error": "Ошибка сети", + "Columns settings": "Настройка колонок", + "Available columns": "Доступные колонки", + "Visible columns": "Видимые колонки", + "Add all": "Добавить все", + "Hide all": "Скрыть все", + "No available columns": "Нет доступных колонок", + "Move columns here": "Переместите колонки сюда", + "Save changes": "Сохранить изменения", + "Apply": "Применить", + "Cancel": "Отмена", + "conditions": "условий", + "modified": "изменён", + "Private filter": "Приватный фильтр", + "For groups": "Фильтр для группы", + "Public filter": "Публичный фильтр", + "Only for you": "Только для вас", + "For selected groups": "Для выбранных групп", + "For all users": "Для всех пользователей", + "Visible only to you": "Виден только вам", + "Visible only to your group": "Виден только для вашей группы", + "API access (feed)": "API-доступ (фид)", "Hide": "Скрыть", "Show": "Показать", - "No notifications found": "Уведомления не найдены", - "Changes not found": "Изменения не найдены", - "Old": "Старое", - "New": "Новое", - "Added": "Добавлено", - "Removed": "Удалено", - "Updated": "Обновлено", - "changed type": "изменен тип", - "Error: Invalid field data": "Ошибка: некорректные данные поля", - "Error: Fields data is invalid": "Ошибка: данные полей некорректны", - "No fields to display": "Нет полей для отображения", - "Error: Some fields are missing required properties (name, type, label)": "Ошибка: в некоторых полях отсутствуют обязательные свойства (name, type, label)", - "I'm not a robot": "Я не робот", - "Solving CAPTCHA...": "Решаем CAPTCHA...", - "Verification complete!": "Проверка завершена!", - "Error solving CAPTCHA. Try again.": "Ошибка решения CAPTCHA. Попробуйте снова.", - "Invalid CAPTCHA solution": "Неверное решение CAPTCHA", - "Wrong username": "Неверный логин", - "Wrong password": "Неверный пароль", - "Profile is not confirmed, please contact to administrator": "Профиль не подтвержден, обратитесь к администратору", - "Profile expired, contact the administrator": "Срок действия профиля истек, обратитесь к администратору", - "The user is not allowed to enter, please contact the administrator": "Пользователю запрещен вход, обратитесь к администратору", - "On": "Вкл", - "Off": "Выкл", - "Log out": "Выйти", - "Adminpanel": "Админ-панель", - "Visible": "Видимость", - "The file used by the models": "Файл используется в моделях", - "This is opensource project": "Это проект с открытым исходным кодом", + "Regenerate": "Перегенерировать", + "copied to clipboard": "скопирован в буфер обмена", + "Failed to copy": "Не удалось скопировать", + "Loading filters...": "Загрузка фильтров...", + "No saved filters": "Нет сохранённых фильтров", + "Save current filter for quick access": "Сохраните текущий фильтр для быстрого доступа", + "Someone else's private filter (owner:": "Чужой приватный фильтр (владелец:", + "unknown)": "неизвестно)", + "Owner": "Владелец", + "Apply filter": "Применить фильтр", + "Delete filter": "Удалить фильтр", + "No permission to delete filter": "Нет прав на удаление фильтра", + "Edit filter": "Редактирование фильтра", + "New filter": "Новый фильтр", + "Filter conditions": "Условия фильтра", + "Add condition": "Добавить условие", + "Select field": "Выберите поле", + "No conditions. Add a condition above.": "Нет условий. Добавьте условие выше.", + "Description": "Описание", + "Icon": "Иконка", + "Color": "Цвет", + "Filter parameters": "Параметры фильтра", + "Change filter conditions and parameters": "Измените условия и параметры фильтра", + "Configure filter conditions": "Настройте условия фильтрации", + "Specify name and parameters for saving": "Укажите название и параметры фильтра для сохранения", + "Someone else's private filter": "Чужой приватный фильтр", + "You cannot change someone else's filter visibility": "Вы не можете изменить видимость чужого фильтра", + "Private": "Приватный", + "Public": "Публичный", + "Visibility": "Видимость", + "Export": "Экспорт", + "Exporting...": "Экспорт...", + "Export is only available with an active filter": "Экспорт доступен только при активном фильтре", + "Export to": "Экспорт в", + "completed": "завершён", + "Export error": "Ошибка экспорта", + "Conditions": "Условия", + "Access for groups": "Доступ для групп", + "Selected groups": "Выбранные группы", + "Groups list": "Список групп с чекбоксами", + "Columns": "Колонки", + "Saving...": "Сохранение...", + "Error saving filter": "Ошибка при сохранении", + "Column configuration": "Настройка колонок", + "Select and arrange columns to display in the table": "Выберите и упорядочите колонки для отображения в таблице", + "Loading columns...": "Загрузка колонок...", + "Use buttons": "Используйте кнопки", + "to move and": "для перемещения и", + "to change order": "для изменения порядка", + "Not contains": "не содержит", + "Data in JSON format": "Данные в формате JSON", + "Text format CSV": "Текстовый формат CSV", + "Excel file": "Excel файл", + "Select a saved filter or create a new one": "Выберите сохранённый фильтр или создайте новый", + "Select a filter or create a new one": "Выберите фильтр или создайте новый", + "User Filters": "Фильтры пользователя", + "Search filters...": "Поиск фильтров...", + "All models": "Все модели", + "No filters found": "Фильтры не найдены", + "Loading more...": "Загрузка...", + "Filter Name": "Название фильтра", "Groups": "Группы", + "You": "Вы", + "Unknown": "Неизвестно", + "found": "найдено", + "JSON URL": "JSON URL", + "XML URL": "XML URL", + "API key is not created": "API ключ не создан", + "The key is not created. Create it or contact an administrator.": "Ключ не создан. Создайте его или обратитесь к администратору.", + "OK": "ОК", + "Save filter": "Сохранить фильтр", + "and": "и", + "Add": "Добавить", + "Custom filtering": "Пользовательская фильтрация", + "Error": "Ошибка", + "Error applying filter": "Ошибка при применении фильтра", + "Model not found": "Модель не найдена", + "Unauthorized": "Не авторизован", + "Forbidden": "Доступ запрещён", + "Conditions are required": "Условия обязательны", + "Filter name is required": "Название фильтра обязательно", + "Filter conditions are required": "Условия фильтра обязательны", + "Filter with this name already exists": "Фильтр с таким названием уже существует", + "Filter not found": "Фильтр не найден", + "Failed to save filter": "Не удалось сохранить фильтр", + "Filter ID is required": "ID фильтра обязателен", + "Failed to delete filter": "Не удалось удалить фильтр", + "Columns array is required": "Массив колонок обязателен", + "Filter not found or access denied": "Фильтр не найден или доступ запрещён", + "Failed to update filter columns": "Не удалось обновить колонки фильтра", + "GroupAP model not found": "Модель GroupAP не найдена", + "Failed to load groups": "Не удалось загрузить группы", + "Internal server error": "Внутренняя ошибка сервера", + "Filter visibility": "Видимость фильтра", "System": "Система", - "Table": "Таблица", - "Tile": "Плитка", - "Load more": "Загрузить еще", - "Images": "Изображения", - "Videos": "Видео", - "Texts": "Тексты", - "Applications": "Приложения", - "File": "Файл", - "Name": "Название", - "Type": "Тип", - "Size (orig.)": "Размер (ориг.)", - "W x H (orig.)": "Ш x В (ориг.)", "W x H": "Ш x В", "Sizes": "Размеры", - "Sizes/Ver": "Размеры/Версии", - "Locales": "Локали", - "Data": "Данные", - "Date": "Дата", "User login": "Логин пользователя", "avatar": "аватар", "expires": "истекает", "is administrator": "администратор", "widgets": "виджеты", "isConfirmed": "подтвержден", - "id": "id", - "title": "заголовок", - "slug": "slug", - "owner": "владелец", "userAPs": "пользователи", "schema": "схема", "example": "пример", "Navigation": "Навигация", "All controlss": "Все контролы", "Categorys": "Категории", - "Textarea": "Текстовое поле", - "Disabled": "Отключено", - "Boolean": "Булево", - "time": "время", - "Number": "Число", - "Editor": "Редактор", - "Date and time": "Дата и время", "First": "Первая", "Last": "Последняя", "Previous": "Назад", "Next": "Вперед", "Notifications": "Уведомления", - "Global Settings": "Глобальные настройки", "Send feedback": "Отправить отзыв", "Feedback": "Обратная связь", - "Description": "Описание", "Attach files": "Прикрепить файлы", "Send": "Отправить", "Sending...": "Отправка...", @@ -178,4 +325,4 @@ "File size exceeds the 5 MB limit": "Размер файла превышает 5 МБ", "Remove file": "Удалить файл", "Max 5 MB per file": "Макс. 5 МБ на файл" -} \ No newline at end of file +} diff --git a/src/tsconfig.json b/src/tsconfig.json index cc9ce9e1..e214317d 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "module": "ESNext", "target": "ESNext", - "moduleResolution": "Node", + "moduleResolution": "bundler", + // "ignoreDeprecations": "6.0", "esModuleInterop": true, "sourceMap": false, "declaration": true, @@ -10,11 +11,16 @@ "skipLibCheck": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - // "strict": true, + "strict": false, + "strictNullChecks": false, + "useUnknownInCatchVariables": false, "alwaysStrict": true, "noImplicitAny": true, "noImplicitReturns": true, - "lib": ["dom", "esnext"], + "lib": [ + "dom", + "esnext" + ], "rootDir": "./", // "baseUrl": "./", "outDir": "../dist", @@ -23,7 +29,14 @@ "esm": true, "experimentalSpecifierResolution": "node" }, - "lib": ["esnext"], - "exclude": ["node_modules", "assets/**/*"], - "include": ["**/*.ts"] -} + "lib": [ + "esnext" + ], + "exclude": [ + "node_modules", + "assets/**/*" + ], + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/src/types/session.d.ts b/src/types/session.d.ts new file mode 100644 index 00000000..8ffad3be --- /dev/null +++ b/src/types/session.d.ts @@ -0,0 +1,14 @@ +// Type definitions for express-session extension +import 'express-session'; +import type { FilterCondition } from '../models/FilterAP'; +import type { FilterColumnAP } from '../models/FilterColumnAP'; + +declare module 'express-session' { + interface SessionData { + temporaryFilters?: Record; + } +} diff --git a/test/adminizer-core.spec.ts b/test/adminizer-core.spec.ts new file mode 100644 index 00000000..a9bbc4d4 --- /dev/null +++ b/test/adminizer-core.spec.ts @@ -0,0 +1,194 @@ +import { describe, expect, it } from "vitest"; +import { DataTypes, Op, Sequelize } from "sequelize"; +import * as publicApi from "../src/index"; +import { + mapSequelizeToAdminizerAttributes, + SequelizeModel, +} from "../src/lib/model/adapter/sequelize"; +import { DataAccessor } from "../src/lib/DataAccessor"; + +describe("Adminizer core ORM contract", () => { + function createModels() { + const sequelize = new Sequelize({ + dialect: "sqlite", + storage: ":memory:", + logging: false, + }); + + const User = sequelize.define("User", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + login: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + }); + + const Post = sequelize.define("Post", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + title: { + type: DataTypes.STRING, + allowNull: false, + }, + views: { + type: DataTypes.INTEGER, + allowNull: true, + }, + published: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + }); + + Post.belongsTo(User, { as: "owner", foreignKey: "ownerId" }); + User.hasMany(Post, { as: "posts", foreignKey: "ownerId" }); + + return { Post }; + } + + it("does not expose Waterline from the public API", () => { + expect(publicApi).not.toHaveProperty("WaterlineAdapter"); + expect(publicApi).not.toHaveProperty("WaterlineModel"); + }); + + it("maps Sequelize metadata to Adminizer attributes", () => { + const { Post } = createModels(); + const attributes = mapSequelizeToAdminizerAttributes(Post as any); + + expect(attributes.id.type).toBe("number"); + expect(attributes.title).toMatchObject({ + type: "string", + required: true, + allowNull: false, + }); + expect(attributes.views).toMatchObject({ + type: "number", + required: false, + allowNull: true, + }); + expect(attributes.published.type).toBe("boolean"); + expect(attributes.ownerId.primaryKeyForAssociation).toBe(true); + expect(attributes.owner).toMatchObject({ + type: "association", + model: "User", + via: "ownerId", + }); + }); + + it("converts adapter-neutral criteria to Sequelize options", () => { + const { Post } = createModels(); + const model = new SequelizeModel("Post", Post as any); + + const options = (model as any)._convertAdminizerCriteriaToSequelizeOptions({ + where: { + owner: 7, + title: { contains: "hello" }, + views: { ">=": 10 }, + published: true, + }, + skip: 20, + limit: 10, + sort: "views DESC", + }); + + expect(options.offset).toBe(20); + expect(options.limit).toBe(10); + expect(options.order).toEqual([["views", "DESC"]]); + expect(options.where.ownerId).toBe(7); + expect(options.where.title[Op.like]).toBe("%hello%"); + expect(options.where.views[Op.gte]).toBe(10); + expect(options.where.published).toBe(true); + }); + + it("preserves Sequelize literal filters while sanitizing access criteria", async () => { + const literal = Sequelize.literal("1=1"); + const accessor = Object.create(DataAccessor.prototype) as DataAccessor; + + Object.assign(accessor, { + user: { isAdministrator: true }, + entity: { + model: { modelname: "Example" }, + config: {}, + }, + }); + + const sanitized = await accessor.sanitizeUserRelationAccess({ + where: literal as any, + }); + + expect(sanitized.where).toBe(literal); + }); + + it("combines Sequelize literal filters with user relation access", async () => { + const literal = Sequelize.literal("1=1"); + const accessor = Object.create(DataAccessor.prototype) as DataAccessor; + + Object.assign(accessor, { + user: { + id: 5, + login: "editor", + isAdministrator: false, + }, + entity: { + model: { + modelname: "Example", + attributes: { + owner: { model: "UserAP" }, + }, + }, + config: { + userAccessRelation: "owner", + }, + }, + }); + + const sanitized = await accessor.sanitizeUserRelationAccess({ + where: literal as any, + }); + + expect((sanitized.where as any).and).toEqual([ + literal, + { owner: 5 }, + ]); + }); + + it("matches JSON array values with jsonContains on SQLite", async () => { + const sequelize = new Sequelize({ + dialect: "sqlite", + storage: ":memory:", + logging: false, + }); + + const Example = sequelize.define("Example", { + selectMany: DataTypes.JSON, + }); + Example.belongsTo(Example, { as: "parent", foreignKey: "parentId" }); + + await sequelize.sync(); + await Example.bulkCreate([ + { selectMany: ["Sone", "Stwo"] }, + { selectMany: ["Sthree"] }, + ]); + + const model = new SequelizeModel("Example", Example as any); + const count = await (model as any)._count({ + where: { + or: [ + { selectMany: { jsonContains: "Sone" } }, + { selectMany: { jsonContains: "Stwo" } }, + ], + }, + }); + + expect(count).toBe(1); + }); +}); diff --git a/test/dataAccesor.spec.ts b/test/dataAccesor.spec.ts deleted file mode 100644 index 3cffe241..00000000 --- a/test/dataAccesor.spec.ts +++ /dev/null @@ -1,242 +0,0 @@ - -import { describe, it, expect, beforeEach, beforeAll } from 'vitest'; -import { UserAP } from '../src/models/UserAP'; -import { DataAccessor } from '../src/lib/DataAccessor.ts'; - -import { Config } from "waterline"; - -// @ts-ignore -import sailsDisk from "sails-disk"; -import { Adminizer, WaterlineAdapter } from '../src'; -import { Entity } from '../src/interfaces/types'; -import { config } from "./datamocks/adminizerConfig" -import Test from './datamocks/Test'; -import Waterline from 'waterline'; -import Example from './datamocks/Example'; -import { ControllerHelper } from 'src/helpers/controllerHelper'; -import { buildMockReq } from './datamocks/buildMockReq'; -const tokens = ["create-test-model", "read-test-model", "update-test-model", "delete-test-model"] -describe('DataAccessor test', () => { - let adminUser: UserAP; - let editorUser: UserAP; - let managerUser: UserAP; - let defaultUser: UserAP; - let entity: Entity; - let instance: DataAccessor; - let adminizer!: Adminizer; - let orm: Waterline.Waterline; - - beforeAll(async () => { - orm = new Waterline(); - - // @ts-ignore - await WaterlineAdapter.registerSystemModels(orm); - await sleep(1000) - orm.registerModel(Test); - orm.registerModel(Example); - const waterlineConfig: Config = { - adapters: { - disk: sailsDisk, - }, - datastores: { - default: { - adapter: "disk", - // @ts-ignore - inMemoryOnly: true, - } - } - }; - const ontology = await new Promise((resolve, reject) => { - orm.initialize(waterlineConfig, (err, ontology) => { - if (err) return reject(err); - resolve(ontology); - }); - }); - const waterlineAdapter = new WaterlineAdapter({ orm, ontology }); - adminizer = new Adminizer([waterlineAdapter]); - - try { - // @ts-ignore - await adminizer.init(config); - } catch (error) { - console.error("adminizer init error:", error); - } - }); - - - beforeEach(() => { - adminUser = { - id: 0, - login: 'adminUser', - isAdministrator: true, - groups: [{ name: 'admin', id: 0 }] - }; - - editorUser = { - id: 1, - login: 'editorUser', - groups: [{ name: 'editor', id: 1, tokens }] - }; - - managerUser = { - id: 2, - login: 'managerUser', - groups: [{ name: 'manager', id: 2, tokens }] - }; - - defaultUser = { - id: 3, - login: 'defaultUser', - groups: [{ name: 'default', id: 3 }] - }; - - entity = ControllerHelper.findEntityObject(buildMockReq(adminizer, "/admin/model/test")); - }); - - it('groupsAccessRights field config checks works for plain data', () => { - - // Add - instance = new DataAccessor(adminizer, adminUser, entity, 'add'); - expect(instance.getFieldsConfig()).toHaveProperty('guardedField'); - - instance = new DataAccessor(adminizer, editorUser, entity, 'add'); - expect(instance.getFieldsConfig()).toHaveProperty('guardedField'); - - instance = new DataAccessor(adminizer, managerUser, entity, 'add'); - expect(instance.getFieldsConfig()).not.toHaveProperty('guardedField'); - - instance = new DataAccessor(adminizer, defaultUser, entity, 'add'); - expect(instance.getFieldsConfig()).toBeUndefined(); - - // Edit - instance = new DataAccessor(adminizer, adminUser, entity, 'edit'); - expect(instance.getFieldsConfig()).toHaveProperty('guardedField'); - - instance = new DataAccessor(adminizer, managerUser, entity, 'edit'); - expect(instance.getFieldsConfig()).toHaveProperty('guardedField'); - - instance = new DataAccessor(adminizer, editorUser, entity, 'edit'); - expect(instance.getFieldsConfig()).not.toHaveProperty('guardedField'); - - instance = new DataAccessor(adminizer, defaultUser, entity, 'edit'); - expect(instance.getFieldsConfig()).toBeUndefined(); - }); - - it('Populated selfAssociation has guardedField for admin', () => { - instance = new DataAccessor(adminizer, adminUser, entity, 'edit'); - expect(instance.getFieldsConfig().selfAssociation.populated).toHaveProperty('guardedField'); - }); - - it('Populated selfAssociation does not expose guardedField to manager', () => { - instance = new DataAccessor(adminizer, managerUser, entity, 'add'); - const populated = instance.getFieldsConfig().selfAssociation.populated; - expect(populated).not.toHaveProperty('guardedField'); - }); - - it('`process()` filters fields based on user access', () => { - const record = { - id: 1, - title: 'Test Title', - guardedField: 'secret', - color: '#000' - }; - - instance = new DataAccessor(adminizer, editorUser, entity, 'edit'); - const result = instance.process(record); - expect(result).not.toHaveProperty('guardedField'); - expect(result).toHaveProperty('title'); - expect(result).toHaveProperty('color'); - }); - - it('`processMany()` filters multiple records', () => { - const records = [ - { id: 1, title: 'One', guardedField: 'X' }, - { id: 2, title: 'Two', guardedField: 'Y' } - ]; - - instance = new DataAccessor(adminizer, editorUser, entity, 'add'); - const result = instance.processMany(records); - expect(result).toHaveLength(2); - expect(result[0]).toHaveProperty('guardedField'); - }); - - it('`sanitizeUserRelationAccess()` includes user ID in criteria', async () => { - // if (entity.config && entity.config.userAccessRelation) { - // entity.config.userAccessRelation = { - // field: 'userField' - // }; - // } - instance = new DataAccessor(adminizer, managerUser, entity, 'add'); - const result = await instance.sanitizeUserRelationAccess({}); - expect(result).toEqual({ userField: 2 }); - }); - - it('`sanitizeUserRelationAccess()` throws error on invalid config', async () => { - if(!entity.config) throw null // Type check - entity.config.userAccessRelation = 'invalidField'; - instance = new DataAccessor(adminizer, managerUser, entity, 'add'); - await expect(instance.sanitizeUserRelationAccess({})).rejects.toThrow( - /Invalid userAccessRelation configuration/ - ); - }); - - it('`setUserRelationAccess()` assigns user to field if userap', async () => { - entity.config.userAccessRelation = 'userField'; - entity.model.attributes.userField = { model: 'UserAP' }; - - const record = { title: 'Sample' }; - instance = new DataAccessor(adminizer, editorUser, entity, 'add'); - const result = await instance.setUserRelationAccess(record); - expect(result).toHaveProperty('userField', 1); - }); - - it('`setUserRelationAccess()` assigns group if groupap and one group', async () => { - entity.config.userAccessRelation = 'groupField'; - entity.model.attributes.groupField = { model: 'GroupAP' }; - - const record = { title: 'With group' }; - instance = new DataAccessor(adminizer, managerUser, entity, 'add'); - const result = await instance.setUserRelationAccess(record); - expect(result).toHaveProperty('groupField', 2); - }); - - it('`setUserRelationAccess()` throws error if multiple groups and groupap', async () => { - const multiGroupUser = { - ...managerUser, - groups: [{ name: 'one', id: 10 }, { name: 'two', id: 20 }] - }; - entity.config.userAccessRelation = 'groupField'; - entity.model.attributes.groupField = { model: 'GroupAP' }; - - instance = new DataAccessor(adminizer, multiGroupUser, entity, 'add'); - await expect(instance.setUserRelationAccess({})).rejects.toThrow( - /associated with none or multiple groups/ - ); - }); - - it('sanitizes access to only own records (userAccessRelation)', async () => { - instance = new DataAccessor(adminizer, editorUser, entity, "list"); - - const criteria = await instance.sanitizeUserRelationAccess({}); - // We expect that the criteria are filtered by user.id - expect(criteria).toHaveProperty("userField"); - expect(criteria["userField"]).toBe(editorUser.id); - }); - - it('sets userAccessRelation automatically when creating record', async () => { - instance = new DataAccessor(adminizer, editorUser, entity, "add"); - - const record = await instance.setUserRelationAccess({ - title: "Test Title", - userField: 999 - }); - - expect(record).toHaveProperty("userField"); - expect(record.userField).toBe(editorUser.id); - }); -}); - - -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} \ No newline at end of file diff --git a/test/datamocks/Example.ts b/test/datamocks/Example.ts deleted file mode 100644 index 9e7bef0c..00000000 --- a/test/datamocks/Example.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Waterline from "waterline"; - -const Example = Waterline.Collection.extend({ - identity: "example", - datastore: "default", - primaryKey: "id", - - attributes: { - id: { - type: "number", - //@ts-ignore - autoMigrations: { autoIncrement: true } - }, - - title: { - type: "string", - required: true - }, - - description: { - type: "string" - }, - - // Feedback (1:* with Test) - tests: { - collection: "Test", - via: "example" - } - } -}); - -export default Example; diff --git a/test/datamocks/Test.ts b/test/datamocks/Test.ts deleted file mode 100644 index 6b57f1c0..00000000 --- a/test/datamocks/Test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import Waterline from "waterline"; - -const Test = Waterline.Collection.extend({ - identity: "Test", - datastore: "default", - primaryKey: "id", - - attributes: { - // ID - id: { - type: "number", - //@ts-ignore - autoMigrations: { autoIncrement: true }, - }, - - // Primitive fields - title: { - type: "string", - required: true, - }, - - number: { - type: "number", - allowNull: true, - }, - - color: { - type: "string", - allowNull: true, - }, - - guardedField: { - type: "string", - allowNull: true, - }, - - // Self-referential association - selfAssociation: { - model: "test", - }, - - // One-way communication on Example - example: { - model: "example", - }, - - // Association to UserAP (owner) - userField: { - model: "userap", - }, - - // Many users (many-to-many) - // @ts-ignore - userAPs: { - collection: "userap" - } - }, -}); - -export default Test; diff --git a/test/datamocks/adminizerConfig.ts b/test/datamocks/adminizerConfig.ts deleted file mode 100644 index fede95a4..00000000 --- a/test/datamocks/adminizerConfig.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { AdminizerConfig, AdminpanelConfig } from "../../src"; - -const models: AdminizerConfig["models"] = { - test: { - title: 'Test model', - model: 'Test', - userAccessRelation: 'userField', - fields: { - createdAt: false, - updatedAt: false, - title: { - title: 'Title', - type: 'string', - required: true - } - }, - list: { - fields: { - userField: false, - } - }, - add: { - fields: { - ownerId: false, - exampleId: false, - guardedField: { - title: "Restricted Field", - groupsAccessRights: ["admin", "editor"] - } - } - }, - edit: { - fields: { - guardedField: { - title: "Restricted Field", - groupsAccessRights: ["admin", "manager"] - } - } - }, - icon: 'receipt' - }, - example: { - title: 'All controls', - model: 'Example', - fields: { - createdAt: false, - updatedAt: false, - title: { - title: 'Title', - type: 'string', - required: true - }, - description: { - title: 'Textarea', - type: 'text', - required: true, - tooltip: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic, nisi.' - }, - sort: { - type: 'boolean', - title: 'Boolean' - }, - disabled_text: { - title: 'Disabled', - type: 'text', - disabled: true, - tooltip: 'This field should be disabled' - }, - range: { - type: 'range', - title: 'Range', - options: { - min: 10, - max: 80 - } - }, - select: { - title: 'Select', - isIn: ['one', 'two', 'three'] - }, - date: { - title: 'Date', - type: 'date', - }, - month: { - title: 'Month', - type: 'month', - }, - datetime: { - title: 'Date and time', - type: 'datetime', - }, - time: { - title: 'time', - type: 'time', - }, - number: { - title: 'Number', - type: 'number', - }, - color: { - title: 'color', - type: 'color', - }, - week: { - title: 'Week', - type: 'week', - }, - json: { - type: 'jsoneditor' - }, - tui: { - type: 'tuieditor', - options: { - name: 'toast-ui', - config: { - hideModeSwitch: true, - previewStyle: 'vertical', - }, - } - }, - code: { - title: 'Code', - type: 'code', - options: { - name: 'monaco', - config: { - language: 'typescript', - } - } - }, - geojson: { - type: 'geo-polygon', - }, - datatable: { - title: 'Price', - type: 'table', - options: { - config: { - dataSchema: {name: null, footage: null, price: null}, - colHeaders: ['One', 'Two', 'Three'], - columns: [ - {data: 'name'}, - {data: 'footage'}, - {data: 'price'} - ], - } - }, - }, - selectMany: { - title: 'Select many', - isIn: ['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], - type: 'select-many' - }, - checkboxes: { - title: 'Checkboxes', - isIn: ['one', 'two', 'three'] - }, - editor: { - title: 'Editor', - type: 'wysiwyg', - options: { - // name: 'react-quill', - name: 'ckeditor', - config: { - items: [ - // 'sourceEditing', // This is for test, see full list of items in src/lib/controls/wysiwyg/CKeditor.ts - // 'showBlocks', - // '|', - 'heading', - '|', - 'bold', - 'italic', - 'underline', - '|', - // 'horizontalLine', - 'link', - 'insertImageViaUrl', - 'insertTable', - 'blockQuote', - '|', - 'alignment', - '|', - 'bulletedList', - 'numberedList', - 'outdent', - 'indent', - ] - } - } - }, - testRelation: { - title: 'Test one association', - displayModifier: function (data) { - return data?.title; - } - }, - tests: { - title: 'One to many association', - displayModifier: function (data) { - return data?.title; - } - }, - }, - list: { - fields: { - json: false, - tui: false, - geojson: false, - week: false, - color: false, - range: false, - date: false, - month: false, - selectMany: false, - select: false, - dateTime: false, - testRelation: false, - tests: false, - price: false, - code: false, - datatable: false - }, - }, - icon: 'inbox' - } -}; - -export const config: AdminpanelConfig = { - routePrefix: "/admin", - auth: { - enable: true - }, - administrator: { - login: 'admin', - password: 'admin' - }, - models: models, - showVersion: true, -}; - diff --git a/test/datamocks/buildMockReq.ts b/test/datamocks/buildMockReq.ts deleted file mode 100644 index b1beb269..00000000 --- a/test/datamocks/buildMockReq.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Adminizer } from "../../src"; - -export function buildMockReq(adminizer: Adminizer, uri: string): ReqType { - return { - originalUrl: uri, - params: {}, - adminizer: adminizer - } as unknown as ReqType; -} diff --git a/test/mediaManagerHelper.spec.ts b/test/mediaManagerHelper.spec.ts deleted file mode 100644 index 4114e230..00000000 --- a/test/mediaManagerHelper.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {describe, expect, it} from "vitest"; -import { - detachMediaManagerField, - normalizeMediaManagerWidgetData, -} from "../src/lib/media-manager/helpers/MediaManagerHelper"; - -describe("MediaManagerHelper", () => { - it("normalizes a JSON string payload into widget items", () => { - const data = normalizeMediaManagerWidgetData( - JSON.stringify([{id: "file-1", url: "/img/1.jpg"}]), - "images" - ); - - expect(data).toEqual([{id: "file-1", url: "/img/1.jpg"}]); - }); - - it("keeps an array payload unchanged", () => { - const payload = [{id: "file-2", filename: "photo.jpg"}]; - - const data = normalizeMediaManagerWidgetData(payload, "images"); - - expect(data).toBe(payload); - }); - - it("converts an empty payload into an empty relation list", () => { - expect(normalizeMediaManagerWidgetData("", "images")).toEqual([]); - expect(normalizeMediaManagerWidgetData(null, "images")).toEqual([]); - }); - - it("moves mediamanager data out of the ORM payload", () => { - const reqData = { - images: [{id: "file-3", mimeType: "image/jpeg"}], - title: "Dish", - }; - const rawReqData = {...reqData}; - - detachMediaManagerField(reqData, rawReqData, "images"); - - expect(reqData).toEqual({title: "Dish"}); - expect(rawReqData.images).toEqual([{id: "file-3", mimeType: "image/jpeg"}]); - }); - - it("throws for invalid mediamanager JSON", () => { - expect(() => normalizeMediaManagerWidgetData("{bad json}", "images")).toThrow( - "Error assign association-many mediamanager data for images" - ); - }); -}); diff --git a/test/openAiModelService.spec.ts b/test/openAiModelService.spec.ts deleted file mode 100644 index 23c8c97d..00000000 --- a/test/openAiModelService.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -import {describe, expect, it, vi} from 'vitest'; -import {OpenAiModelService} from '../src/lib/ai-assistant/OpenAiModelService'; -import {UserAP} from '../src/models/UserAP'; -import {Adminizer} from '../src/lib/Adminizer'; -import {ActionType, ModelConfig} from '../src/interfaces/adminpanelConfig'; -import {Entity} from '../src/interfaces/types'; -import {DataAccessor} from '../src/lib/DataAccessor'; - -describe('OpenAiModelService', () => { - const createAdminizerStub = () => { - const modelCreate = vi.fn(async (data: Record) => ({id: 42, ...data})); - const stubModel = { - modelname: 'example', - primaryKey: 'id', - create: modelCreate, - }; - - const accessRightsHelper = { - registerToken: vi.fn(), - hasPermission: vi.fn().mockReturnValue(true), - }; - - const config = { - routePrefix: '/adminizer', - models: { - Example: { - model: 'example', - title: 'Example', - add: true, - edit: true, - list: true, - remove: true, - view: true, - } satisfies ModelConfig, - }, - } as Adminizer['config']; - - const adminizer = { - config, - accessRightsHelper, - modelHandler: { - model: { - get: vi.fn(() => stubModel), - }, - }, - } as unknown as Adminizer; - - const user: UserAP = { - id: 1, - login: 'openai', - groups: [], - isAdministrator: false, - } as unknown as UserAP; - - return {adminizer, accessRightsHelper, stubModel, modelCreate, user}; - }; - - it('returns usage instructions when the prompt is not JSON', async () => { - const {adminizer, user} = createAdminizerStub(); - const service = new OpenAiModelService(adminizer, vi.fn()); - - const reply = await service.generateReply('hello world', [], user); - - expect(reply).toContain('Provide JSON instructions'); - }); - - it('creates a record using DataAccessor when JSON command is provided', async () => { - const {adminizer, user, modelCreate} = createAdminizerStub(); - const accessorStub = {} as DataAccessor; - const accessorFactory = vi.fn((entity: Entity, currentUser: UserAP, action: ActionType) => { - expect(entity.name).toBe('Example'); - expect(currentUser).toBe(user); - expect(action).toBe('add'); - return accessorStub; - }); - - const service = new OpenAiModelService(adminizer, accessorFactory); - const payload = { - action: 'create', - entity: 'Example', - data: {title: 'AI title'}, - }; - - const reply = await service.generateReply(JSON.stringify(payload), [], user); - - expect(accessorFactory).toHaveBeenCalledTimes(1); - expect(modelCreate).toHaveBeenCalledWith(payload.data, accessorStub); - expect(reply).toContain('Record created in Example'); - expect(reply).toContain('AI title'); - }); - - it('blocks creation when the user lacks permissions', async () => { - const {adminizer, user, accessRightsHelper} = createAdminizerStub(); - accessRightsHelper.hasPermission.mockReturnValue(false); - - const service = new OpenAiModelService(adminizer, vi.fn()); - const payload = { - action: 'create', - entity: 'Example', - data: {title: 'Restricted'}, - }; - - const reply = await service.generateReply(JSON.stringify(payload), [], user); - - expect(reply).toContain('does not have permission'); - }); -}); diff --git a/test/systemModels.spec.ts b/test/systemModels.spec.ts deleted file mode 100644 index d13478d0..00000000 --- a/test/systemModels.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, beforeAll, afterAll, expect } from 'vitest'; -import Waterline from 'waterline'; -// @ts-ignore -import sailsDisk from 'sails-disk'; -import { Sequelize } from 'sequelize-typescript'; -import { - WaterlineAdapter, - SequelizeAdapter -} from '../src'; - -// Helper to init Waterline ORM -async function initWaterline() { - const orm = new Waterline(); - await WaterlineAdapter.registerSystemModels(orm); - const config = { - adapters: { disk: sailsDisk }, - datastores: { - default: { adapter: 'disk', inMemoryOnly: true } - } - }; - const ontology = await new Promise((resolve, reject) => { - orm.initialize(config as any, (err, ontology) => { - if (err) return reject(err); - resolve(ontology); - }); - }); - return { orm, ontology }; -} - -describe('System models registration', () => { - describe('Waterline', () => { - let orm: Waterline.Waterline; - let ontology: any; - - beforeAll(async () => { - ({ orm, ontology } = await initWaterline()); - }); - - afterAll(async () => { - await orm.teardown(); - }); - - it('registers all system models', () => { - expect(ontology.collections).toHaveProperty('groupap'); - expect(ontology.collections).toHaveProperty('userap'); - expect(ontology.collections).toHaveProperty('mediamanagerap'); - expect(ontology.collections).toHaveProperty('mediamanagerassociationsap'); - expect(ontology.collections).toHaveProperty('mediamanagermetaap'); - expect(ontology.collections).toHaveProperty('navigationap'); - }); - - it('creates records for system models', async () => { - const group = await ontology.collections.groupap.create({ name: 'g1' }).fetch(); - const user = await ontology.collections.userap.create({ login: 'u1', fullName: 'User One' }).fetch(); - const nav = await ontology.collections.navigationap.create({ label: 'main', tree: {} }).fetch(); - const file = await ontology.collections.mediamanagerap.create({ id: 'file-1' }).fetch(); - const assoc = await ontology.collections.mediamanagerassociationsap.create({ id: 'assoc-1' }).fetch(); - const meta = await ontology.collections.mediamanagermetaap.create({ id: 'meta-1' }).fetch(); - - expect(group).toHaveProperty('id'); - expect(user).toHaveProperty('id'); - expect(nav).toHaveProperty('id'); - expect(file).toHaveProperty('id'); - expect(assoc).toHaveProperty('id'); - expect(meta).toHaveProperty('id'); - }); - }); - - describe('Sequelize', () => { - let orm: Sequelize; - - beforeAll(async () => { - orm = new Sequelize('sqlite::memory:', { logging: false }); - await SequelizeAdapter.registerSystemModels(orm); - }); - - afterAll(async () => { - await orm.close(); - }); - - it('registers all system models', () => { - expect(orm.models).toHaveProperty('GroupAP'); - expect(orm.models).toHaveProperty('UserAP'); - expect(orm.models).toHaveProperty('MediaManagerAP'); - expect(orm.models).toHaveProperty('MediaManagerAssociationsAP'); - expect(orm.models).toHaveProperty('MediaManagerMetaAP'); - expect(orm.models).toHaveProperty('NavigationAP'); - }); - - it('creates records for system models', async () => { - const { GroupAP, UserAP, NavigationAP, MediaManagerAP, MediaManagerAssociationsAP, MediaManagerMetaAP } = orm.models as any; - const group = await GroupAP.create({ name: 'g2' }); - const user = await UserAP.create({ login: 'u2', fullName: 'User Two' }); - const nav = await NavigationAP.create({ label: 'nav', tree: {} }); - const file = await MediaManagerAP.create({ id: 'file-2' }); - const assoc = await MediaManagerAssociationsAP.create({ id: 'assoc-2' }); - const meta = await MediaManagerMetaAP.create({ id: 'meta-2' }); - - expect(group.id).toBeDefined(); - expect(user.id).toBeDefined(); - expect(nav.id).toBeDefined(); - expect(file.id).toBeDefined(); - expect(assoc.id).toBeDefined(); - expect(meta.id).toBeDefined(); - }); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 143d5ee9..32d1ef76 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,12 +15,13 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "baseUrl": ".", + // "baseUrl": ".", "paths": { "@/*": ["./src/assets/js/*"] } }, "exclude": [ "node_modules", + "dist", ] } diff --git a/vite.config.ts b/vite.config.ts index b7df0758..a98b4e9a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,15 +4,6 @@ import path from "path"; import tailwindcss from "@tailwindcss/vite"; import packageJson from './package.json'; -const ReactConfig = { - // Disable React compiler to reduce memory usage - // babel: { - // plugins: [ - // ['babel-plugin-react-compiler'], - // ] - // } -}; - export default defineConfig({ define: { @@ -37,14 +28,14 @@ export default defineConfig({ manualChunks: { vendor: ['react', 'react-dom'], monaco: ['@monaco-editor/react'], - handsontable: ['@handsontable/react', 'handsontable'], + handsontable: ['@handsontable/react-wrapper', 'handsontable'], jsoneditor: ['vanilla-jsoneditor'] } }, }, }, plugins: [ - react(ReactConfig), + react(), tailwindcss(), ], esbuild: {