web-mojo 2.2.12 → 2.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.es.js +4 -4
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/{ChatView-CQnDGafI.js → ChatView-DGulpthL.js} +2 -2
- package/dist/chunks/{ChatView-CQnDGafI.js.map → ChatView-DGulpthL.js.map} +1 -1
- package/dist/chunks/{ChatView-ppMlENSa.js → ChatView-eFzjsHBL.js} +6 -6
- package/dist/chunks/{ChatView-ppMlENSa.js.map → ChatView-eFzjsHBL.js.map} +1 -1
- package/dist/chunks/{Collection-BQxqHtRi.js → Collection-CTkDG1NZ.js} +2 -2
- package/dist/chunks/{Collection-BQxqHtRi.js.map → Collection-CTkDG1NZ.js.map} +1 -1
- package/dist/chunks/{ContextMenu-BNFU-kG4.js → ContextMenu-Capwv7d-.js} +2 -2
- package/dist/chunks/{ContextMenu-BNFU-kG4.js.map → ContextMenu-Capwv7d-.js.map} +1 -1
- package/dist/chunks/{ListView-B96JeG4g.js → ListView-CNkYumcc.js} +2 -2
- package/dist/chunks/{ListView-B96JeG4g.js.map → ListView-CNkYumcc.js.map} +1 -1
- package/dist/chunks/{TokenManager-TEF4Gmwu.js → TokenManager-CBXqj6Iw.js} +3 -3
- package/dist/chunks/{TokenManager-TEF4Gmwu.js.map → TokenManager-CBXqj6Iw.js.map} +1 -1
- package/dist/chunks/{version-C2aAPoA6.js → version-DCTYSNWj.js} +4 -4
- package/dist/chunks/{version-C2aAPoA6.js.map → version-DCTYSNWj.js.map} +1 -1
- package/dist/chunks/{version-CiqJg8U3.js → version-DnlcM3tJ.js} +2 -2
- package/dist/chunks/{version-CiqJg8U3.js.map → version-DnlcM3tJ.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +4 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +8 -8
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.es.js +2 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatView-CQnDGafI.js","sources":["../../src/core/models/AWS.js","../../src/core/models/Email.js","../../src/core/views/feedback/ProgressView.js","../../src/core/services/FileUpload.js","../../src/core/models/Files.js","../../src/core/models/Incident.js","../../src/core/models/Job.js","../../src/core/models/JobRunner.js","../../src/core/models/Log.js","../../src/core/models/Member.js","../../src/core/models/Metrics.js","../../src/core/models/Push.js","../../src/core/models/System.js","../../src/core/models/Tickets.js","../../src/core/views/table/TableRow.js","../../src/core/utils/DjangoLookups.js","../../src/core/views/table/TableView.js","../../src/core/pages/TablePage.js","../../src/core/views/navigation/TabView.js","../../src/core/views/data/FilePreviewView.js","../../src/core/views/chat/ChatMessageView.js","../../src/core/views/chat/ChatInputView.js","../../src/core/views/chat/ChatView.js"],"sourcesContent":["\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass S3Bucket extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/aws/s3/bucket',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass S3BucketList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: S3Bucket,\n endpoint: '/api/aws/s3/bucket',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst S3BucketForms = {\n create: {\n title: 'Add S3 Bucket',\n fields: [\n {\n name: 'bucket_name',\n type: 'text',\n label: 'Name',\n placeholder: 'bucket name',\n help: 'Enter a universally unique name for the bucket',\n required: true,\n cols: 12,\n },\n {\n name: 'is_public',\n type: 'switch',\n label: 'Is Public',\n cols: 12,\n },\n ],\n },\n\n // Provide an edit form even though legacy only had ADD_FORM\n edit: {\n title: 'Edit S3 Bucket',\n fields: [\n {\n name: 'bucket_name',\n type: 'text',\n label: 'Name',\n placeholder: 'bucket name',\n help: 'Enter a universally unique name for the bucket',\n required: true,\n cols: 12,\n },\n {\n name: 'is_public',\n type: 'switch',\n label: 'Is Public',\n cols: 12,\n },\n ],\n },\n};\n\nexport { S3Bucket, S3BucketList, S3BucketForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\n\n/**\n * EmailDomain - SES/SNS/S3-backed email domain model\n * Maps to REST endpoints under /api/aws/email/domain\n *\n * Key operations:\n * - Create/Update/Delete domains\n * - Onboard: DNS records + SNS + optional receiving\n * - Audit: Drift report (verification/DKIM, topics, receipt rules)\n * - Reconcile: Safe, idempotent fixes (no DNS writes)\n *\n * Notes:\n * - Management endpoints require \"manage_aws\" permission server-side.\n * - Error handling follows the MOJO Rest response contract.\n */\nclass EmailDomain extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/domain',\n ...options\n });\n }\n\n /**\n * Onboard the domain (DNS/SNS/receiving orchestration).\n * POST /api/aws/email/domain/<id>/onboard\n *\n * @param {object} data\n * receiving_enabled?: boolean\n * s3_inbound_bucket?: string\n * s3_inbound_prefix?: string\n * ensure_mail_from?: boolean\n * mail_from_subdomain?: string\n * dns_mode?: \"manual\" | \"godaddy\"\n * godaddy_key?: string\n * godaddy_secret?: string\n * endpoints?: { bounce, complaint, delivery, inbound }\n * @param {object} options - Optional { params?: object }\n * @returns {Promise<object>} REST response\n */\n async onboard(data = {}, options = {}) {\n if (!this.id) {\n await this.showError('Cannot onboard domain without ID');\n return {\n success: false,\n status: 400,\n error: 'Missing domain id'\n };\n }\n\n try {\n const url = `${this.buildUrl(this.id)}/onboard`;\n const response = await this.rest.POST(url, data, options.params);\n\n // No guaranteed shape to merge; response contains DNS records, topics, notes, etc.\n // If backend returns updated domain fields, you can call this.set(response.data.data).\n return response;\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to onboard domain'\n };\n }\n }\n\n /**\n * Audit domain configuration for drift.\n * GET or POST /api/aws/email/domain/<id>/audit\n *\n * @param {object} options\n * method?: 'GET'|'POST' (default 'GET')\n * data?: object (when method is POST)\n * params?: object (query params for GET)\n * @returns {Promise<object>} REST response\n */\n async audit(options = {}) {\n if (!this.id) {\n await this.showError('Cannot audit domain without ID');\n return {\n success: false,\n status: 400,\n error: 'Missing domain id'\n };\n }\n\n const method = (options.method || 'GET').toUpperCase();\n const url = `${this.buildUrl(this.id)}/audit`;\n\n try {\n if (method === 'POST') {\n return await this.rest.POST(url, options.data || {}, options.params);\n }\n return await this.rest.GET(url, options.params);\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to audit domain'\n };\n }\n }\n\n /**\n * Reconcile domain configuration (safe fixes; no DNS writes).\n * POST /api/aws/email/domain/<id>/reconcile\n *\n * @param {object} data - Optional payload (usually none required)\n * @param {object} options - Optional { params?: object }\n * @returns {Promise<object>} REST response\n */\n async reconcile(data = {}, options = {}) {\n if (!this.id) {\n await this.showError('Cannot reconcile domain without ID');\n return {\n success: false,\n status: 400,\n error: 'Missing domain id'\n };\n }\n\n try {\n const url = `${this.buildUrl(this.id)}/reconcile`;\n return await this.rest.POST(url, data, options.params);\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to reconcile domain'\n };\n }\n }\n\n /**\n * Convenience: Onboard by id without creating a separate instance.\n * @param {string|number} id\n * @param {object} data\n * @param {object} options\n */\n static async onboardById(id, data = {}, options = {}) {\n const model = new EmailDomain({ id }, options);\n return await model.onboard(data, options);\n }\n\n /**\n * Convenience: Audit by id without creating a separate instance.\n * @param {string|number} id\n * @param {object} options - see audit()\n */\n static async auditById(id, options = {}) {\n const model = new EmailDomain({ id }, options);\n return await model.audit(options);\n }\n\n /**\n * Convenience: Reconcile by id without creating a separate instance.\n * @param {string|number} id\n * @param {object} data\n * @param {object} options\n */\n static async reconcileById(id, data = {}, options = {}) {\n const model = new EmailDomain({ id }, options);\n return await model.reconcile(data, options);\n }\n}\n\n/**\n * EmailDomainList - Collection of EmailDomain\n * Supports standard MOJO list/search/sort/pagination patterns\n */\nclass EmailDomainList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: EmailDomain,\n endpoint: '/api/aws/email/domain',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for EmailDomain (for TablePage/Dialog integration)\n * NOTE: Conditional requirements (e.g., s3_inbound_bucket when receiving_enabled)\n * should be validated server-side; help text guides the admin.\n */\nconst EmailDomainForms = {\n create: {\n title: 'Add Email Domain',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Domain Name',\n placeholder: 'example.com',\n required: true,\n columns: 12,\n help: 'Enter the root domain to verify with SES (no protocol).'\n },\n {\n name: 'region',\n type: 'text',\n label: 'AWS Region (optional)',\n placeholder: 'us-east-1',\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with SES permissions',\n columns: 12,\n help: 'Optional, AWS Key with SES permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with SES permissions',\n columns: 12,\n help: 'Optional, AWS Secret with SES permissions'\n },\n {\n name: 'receiving_enabled',\n type: 'switch',\n label: 'Enable Inbound Receiving',\n columns: 12,\n help: 'Catch-all SES receipt rule to S3 + SNS; routing is done in-app.'\n }\n ]\n },\n\n edit: {\n title: 'Edit Email Domain',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Domain Name',\n placeholder: 'example.com',\n required: true,\n columns: 12,\n readonly: true,\n help: 'Domain name cannot be changed after creation.'\n },\n {\n name: 'region',\n type: 'text',\n label: 'AWS Region',\n placeholder: 'us-east-1',\n columns: 12\n },\n {\n name: 'receiving_enabled',\n type: 'switch',\n label: 'Enable Inbound Receiving',\n columns: 12\n },\n {\n name: 's3_inbound_bucket',\n type: 'text',\n label: 'Inbound S3 Bucket',\n placeholder: 'my-inbound-bucket',\n columns: 12\n },\n {\n name: 's3_inbound_prefix',\n type: 'text',\n label: 'Inbound S3 Prefix',\n placeholder: 'inbound/example.com/',\n columns: 12\n },\n {\n name: 'dns_mode',\n type: 'select',\n label: 'DNS Mode',\n options: [\n { value: 'manual', text: 'Manual (show records)' },\n { value: 'godaddy', text: 'GoDaddy (apply via API)' }\n ],\n columns: 12\n }\n ],\n },\n\n credentials: {\n fields: [\n {\n name: 'region',\n type: 'select',\n label: 'AWS Region (optional)',\n placeholder: 'us-east-1',\n options: [\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with SES permissions',\n columns: 12,\n help: 'Optional, AWS Key with SES permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with SES permissions',\n columns: 12,\n help: 'Optional, AWS Secret with SES permissions'\n },\n ]\n},\n\n onboard: {\n title: 'Onboard Domain',\n fields: [\n {\n type: 'header',\n text: 'Receiving',\n level: 6,\n className: 'mt-2'\n },\n {\n name: 'receiving_enabled',\n type: 'switch',\n label: 'Enable Inbound Receiving',\n columns: 12\n },\n {\n name: 's3_inbound_bucket',\n type: 'text',\n label: 'Inbound S3 Bucket',\n placeholder: 'my-inbound-bucket',\n columns: 12,\n help: 'Required if receiving is enabled.'\n },\n {\n name: 's3_inbound_prefix',\n type: 'text',\n label: 'Inbound S3 Prefix',\n placeholder: 'inbound/example.com/',\n columns: 12\n },\n\n {\n type: 'header',\n text: 'MAIL FROM (optional)',\n level: 6,\n className: 'mt-3'\n },\n {\n name: 'ensure_mail_from',\n type: 'switch',\n label: 'Ensure MAIL FROM Setup',\n columns: 12\n },\n {\n name: 'mail_from_subdomain',\n type: 'text',\n label: 'MAIL FROM Subdomain',\n placeholder: 'feedback',\n columns: 12\n },\n\n {\n type: 'header',\n text: 'DNS',\n level: 6,\n className: 'mt-3'\n },\n {\n name: 'dns_mode',\n type: 'select',\n label: 'DNS Mode',\n options: [\n { value: 'manual', text: 'Manual (show records)' },\n { value: 'godaddy', text: 'GoDaddy (apply via API)' }\n ],\n value: 'manual',\n columns: 12\n },\n {\n name: 'godaddy_key',\n type: 'text',\n label: 'GoDaddy API Key',\n columns: 12,\n help: 'Required when DNS Mode = GoDaddy.'\n },\n {\n name: 'godaddy_secret',\n type: 'password',\n label: 'GoDaddy API Secret',\n columns: 12\n },\n\n {\n type: 'header',\n text: 'Webhook Endpoints',\n level: 6,\n className: 'mt-3'\n },\n {\n name: 'endpoints.bounce',\n type: 'text',\n label: 'Bounce Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/bounce',\n columns: 12\n },\n {\n name: 'endpoints.complaint',\n type: 'text',\n label: 'Complaint Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/complaint',\n columns: 12\n },\n {\n name: 'endpoints.delivery',\n type: 'text',\n label: 'Delivery Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/delivery',\n columns: 12\n },\n {\n name: 'endpoints.inbound',\n type: 'text',\n label: 'Inbound Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/inbound',\n columns: 12\n }\n ]\n }\n};\n\n/**\n * Mailbox - Represents a single email address within a domain.\n * Endpoint: /api/aws/email/mailbox\n * Fields (typical):\n * - domain: FK (domain id) or name (server may accept name)\n * - email: full address\n * - allow_inbound: boolean\n * - allow_outbound: boolean\n * - async_handler: \"package.module:function\" for task dispatch on inbound\n */\nclass Mailbox extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/mailbox',\n ...options\n });\n }\n}\n\nMailbox.sendEmail = async function(data) {\n return await rest.POST('/api/aws/email/send', data);\n};\n\n/**\n * MailboxList - Collection of Mailboxes\n */\nclass MailboxList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Mailbox,\n endpoint: '/api/aws/email/mailbox',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for Mailbox CRUD\n */\nconst MailboxForms = {\n create: {\n title: 'Add Mailbox',\n fields: [\n {\n type: 'collection',\n name: 'domain',\n label: 'Domain',\n Collection: EmailDomainList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search domains...',\n emptyFetch: false,\n required: true,\n debounceMs: 300, // Search debounce delay\n columns: 12\n },\n {\n name: 'email',\n type: 'email',\n label: 'Email Address',\n placeholder: 'support@example.com',\n required: true,\n columns: 12\n },\n {\n name: 'allow_inbound',\n type: 'switch',\n label: 'Allow Inbound',\n columns: 6\n },\n {\n name: 'allow_outbound',\n type: 'switch',\n label: 'Allow Outbound',\n defaultValue: true,\n columns: 6\n },\n {\n name: 'is_system_default',\n type: 'switch',\n label: 'System Default',\n columns: 6\n },\n {\n name: 'is_domain_default',\n type: 'switch',\n label: 'Domain Default',\n columns: 6\n },\n {\n name: 'async_handler',\n type: 'text',\n label: 'Async Handler (optional)',\n placeholder: 'myapp.handlers.process_support',\n columns: 12,\n help: 'Module:function to process inbound messages via task system'\n }\n ]\n },\n\n edit: {\n title: 'Edit Mailbox',\n fields: [\n {\n name: 'email',\n type: 'email',\n label: 'Email Address',\n required: true,\n columns: 12\n },\n {\n name: 'allow_inbound',\n type: 'switch',\n label: 'Allow Inbound',\n columns: 6\n },\n {\n name: 'allow_outbound',\n type: 'switch',\n label: 'Allow Outbound',\n columns: 6\n },\n {\n name: 'is_system_default',\n type: 'switch',\n label: 'System Default',\n columns: 6\n },\n {\n name: 'is_domain_default',\n type: 'switch',\n label: 'Domain Default',\n columns: 6\n },\n {\n name: 'async_handler',\n type: 'text',\n label: 'Async Handler (optional)',\n placeholder: 'myapp.handlers.process_support',\n columns: 12\n }\n ]\n }\n};\n\n/**\n * SentMessage - Outbound messages sent via SES; read-only in UI\n * Endpoint: /api/aws/email/sent\n */\nclass SentMessage extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/sent',\n ...options\n });\n }\n}\n\n/**\n * SentMessageList - Collection of SentMessage\n */\nclass SentMessageList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: SentMessage,\n endpoint: '/api/aws/email/sent',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms for SentMessage (read-only details)\n */\nconst SentMessageForms = {\n view: {\n title: 'Sent Message Details',\n fields: [\n { name: 'id', type: 'text', label: 'ID', readonly: true, cols: 6 },\n { name: 'ses_message_id', type: 'text', label: 'SES Message ID', readonly: true, cols: 6 },\n { name: 'from_email', type: 'text', label: 'From', readonly: true, cols: 12 },\n { name: 'to', type: 'textarea', label: 'To', readonly: true, rows: 2, cols: 12 },\n { name: 'cc', type: 'textarea', label: 'CC', readonly: true, rows: 2, cols: 12 },\n { name: 'bcc', type: 'textarea', label: 'BCC', readonly: true, rows: 2, cols: 12 },\n { name: 'subject', type: 'text', label: 'Subject', readonly: true, cols: 12 },\n { name: 'status', type: 'text', label: 'Status', readonly: true, cols: 6 },\n { name: 'status_reason', type: 'textarea', label: 'Status Reason', readonly: true, rows: 3, cols: 12 },\n { name: 'created', type: 'text', label: 'Created', readonly: true, cols: 6 }\n ]\n }\n};\n\n/**\n * EmailTemplate - DB template entries (Django templated), CRUD supported\n * Endpoint: /api/aws/email/template\n */\nclass EmailTemplate extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/template',\n ...options\n });\n }\n}\n\n/**\n * EmailTemplateList - Collection of EmailTemplate\n */\nclass EmailTemplateList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: EmailTemplate,\n endpoint: '/api/aws/email/template',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for EmailTemplate CRUD\n */\nconst EmailTemplateForms = {\n create: {\n title: 'Add Email Template',\n fields: [\n { name: 'name', type: 'text', label: 'Name', required: true, cols: 12 },\n { name: 'subject_template', type: 'text', label: 'Subject Template', cols: 12 },\n {\n type: 'tabset',\n name: 'settingsTabs',\n tabs: [\n {\n label: 'HTML',\n fields: [\n { name: 'html_template', type: 'textarea', label: 'HTML Template', rows: 16, cols: 12 },\n ],\n },\n {\n label: 'TEXT',\n fields: [\n { name: 'text_template', type: 'textarea', label: 'Text Template', rows: 16, cols: 12 }\n ]\n }\n ]\n }\n ]\n },\n edit: {\n title: 'Edit Email Template',\n fields: [\n { name: 'name', type: 'text', label: 'Name', required: true, cols: 12 },\n { name: 'subject_template', type: 'text', label: 'Subject Template', cols: 12 },\n {\n type: 'tabset',\n name: 'settingsTabs',\n tabs: [\n {\n label: 'HTML',\n fields: [\n { name: 'html_template', type: 'textarea', label: 'HTML Template', rows: 16, cols: 12 },\n ],\n },\n {\n label: 'TEXT',\n fields: [\n { name: 'text_template', type: 'textarea', label: 'Text Template', rows: 16, cols: 12 }\n ]\n }\n ]\n }\n ]\n }\n};\n\nexport {\n EmailDomain,\n EmailDomainList,\n EmailDomainForms,\n Mailbox,\n MailboxList,\n MailboxForms,\n SentMessage,\n SentMessageList,\n SentMessageForms,\n EmailTemplate,\n EmailTemplateList,\n EmailTemplateForms\n};\n","/**\n * ProgressView - File upload progress component\n * \n * Shows upload progress with progress bar, filename, and cancellation option\n * Integrates with FileUpload service for real-time progress updates\n * \n * Features:\n * - Bootstrap progress bar with percentage\n * - File information (name, size)\n * - Bytes uploaded/total display\n * - Cancel button with confirmation\n * - Responsive design\n * \n * Events:\n * - 'cancel' - Emitted when user cancels upload\n * \n * @example\n * const progressView = new ProgressView({\n * filename: 'document.pdf',\n * filesize: 1024000,\n * onCancel: () => fileUpload.cancel()\n * });\n * \n * // Update progress\n * progressView.updateProgress({ progress: 0.5, loaded: 512000, total: 1024000 });\n */\n\nimport View from '@core/View.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass ProgressView extends View {\n constructor(options = {}) {\n super({\n template: 'progress-view-template',\n ...options\n });\n\n // Initialize progress data\n this.filename = options.filename || 'Unknown file';\n this.filesize = options.filesize || 0;\n this.filesizeFormatted = dataFormatter.pipe(this.filesize, 'filesize');\n \n // Progress state\n this.progress = 0;\n this.percentage = 0;\n this.loaded = 0;\n this.total = this.filesize;\n this.loadedFormatted = '0 B';\n this.totalFormatted = this.filesizeFormatted;\n this.status = 'Starting upload...';\n \n // Options\n this.showCancel = options.showCancel !== false;\n this.onCancel = options.onCancel || null;\n \n // State\n this.cancelled = false;\n this.completed = false;\n }\n\n /**\n * Get template for the progress view\n */\n getTemplate() {\n return `\n <div class=\"progress-view\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div class=\"flex-grow-1 min-width-0\">\n <div class=\"fw-medium text-truncate\" title=\"{{filename}}\">\n <i class=\"bi bi-file-earmark me-1\"></i>\n {{filename}}\n </div>\n <small class=\"text-muted\">{{status}}</small>\n </div>\n {{#showCancel}}\n <button type=\"button\" \n class=\"btn btn-sm btn-outline-secondary ms-2\" \n data-action=\"cancel\"\n {{#cancelled}}disabled{{/cancelled}}>\n <i class=\"bi bi-x\"></i>\n </button>\n {{/showCancel}}\n </div>\n \n <div class=\"progress mb-2\" style=\"height: 8px;\">\n <div class=\"progress-bar\" \n role=\"progressbar\" \n style=\"width: {{percentage}}%\"\n aria-valuenow=\"{{percentage}}\" \n aria-valuemin=\"0\" \n aria-valuemax=\"100\">\n </div>\n </div>\n \n <div class=\"d-flex justify-content-between\">\n <small class=\"text-muted\">\n {{loadedFormatted}} / {{totalFormatted}}\n </small>\n <small class=\"text-muted\">\n {{percentage}}%\n </small>\n </div>\n </div>\n `;\n }\n\n /**\n * Update progress information\n * @param {Object} progressInfo - Progress data\n * @param {number} progressInfo.progress - Progress as decimal (0-1)\n * @param {number} progressInfo.loaded - Bytes loaded\n * @param {number} progressInfo.total - Total bytes\n * @param {number} progressInfo.percentage - Progress as percentage (0-100)\n */\n updateProgress(progressInfo) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n this.progress = progressInfo.progress;\n this.percentage = progressInfo.percentage;\n this.loaded = progressInfo.loaded;\n this.total = progressInfo.total || this.filesize;\n \n // Format bytes for display\n this.loadedFormatted = dataFormatter.pipe(this.loaded, 'filesize');\n this.totalFormatted = dataFormatter.pipe(this.total, 'filesize');\n \n // Update status message\n if (this.percentage < 100) {\n this.status = `Uploading... ${this.percentage}%`;\n } else {\n this.status = 'Finalizing upload...';\n }\n\n // Re-render to show updated progress\n this.render();\n }\n\n /**\n * Mark upload as completed\n * @param {string} message - Success message\n */\n markCompleted(message = 'Upload completed!') {\n this.completed = true;\n this.progress = 1;\n this.percentage = 100;\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as failed\n * @param {string} message - Error message\n */\n markFailed(message = 'Upload failed') {\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as cancelled\n */\n markCancelled() {\n this.cancelled = true;\n this.status = 'Upload cancelled';\n this.render();\n }\n\n /**\n * Handle cancel button click\n * @param {string} action - Action name\n * @param {Event} event - Click event\n * @param {Element} element - Button element\n */\n async onActionCancel(action, event, element) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n // Disable button immediately\n element.disabled = true;\n \n // Mark as cancelled\n this.markCancelled();\n \n // Emit cancel event\n this.emit('cancel');\n \n // Call cancel callback if provided\n if (typeof this.onCancel === 'function') {\n try {\n await this.onCancel();\n } catch (error) {\n console.error('Error in cancel callback:', error);\n }\n }\n }\n\n /**\n * Set filename\n * @param {string} filename - New filename\n */\n setFilename(filename) {\n this.filename = filename;\n this.render();\n }\n\n /**\n * Set file size\n * @param {number} size - File size in bytes\n */\n setFilesize(size) {\n this.filesize = size;\n this.filesizeFormatted = dataFormatter.pipe(size, 'filesize');\n this.total = size;\n this.totalFormatted = this.filesizeFormatted;\n this.render();\n }\n\n /**\n * Get current progress as percentage\n * @returns {number} Progress percentage (0-100)\n */\n getPercentage() {\n return this.percentage;\n }\n\n /**\n * Check if upload is completed\n * @returns {boolean} True if completed\n */\n isCompleted() {\n return this.completed;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.filename,\n filesize: this.filesize,\n progress: this.progress,\n percentage: this.percentage,\n loaded: this.loaded,\n total: this.total,\n cancelled: this.cancelled,\n completed: this.completed,\n status: this.status\n };\n }\n}\n\nexport default ProgressView;","/**\n * FileUpload - File upload service with progress tracking and UI integration\n *\n * Features:\n * - Auto-start upload process\n * - Progress tracking with detailed information\n * - Promise interface with cancellation support\n * - Toast integration for user feedback\n * - Three-stage upload process (initiate → upload → complete)\n *\n * @example\n * const file = new File();\n * const upload = file.upload({\n * file: fileObject,\n * name: 'avatar.jpg',\n * group: 'profile-pics',\n * description: 'User avatar',\n * onProgress: ({ progress, loaded, total, percentage }) => {\n * console.log(`${percentage}% complete`);\n * }\n * });\n *\n * upload.then(result => console.log('Success!'))\n * .catch(error => console.error('Failed:', error));\n */\n\nimport ToastService from './ToastService.js';\nimport ProgressView from '../views/feedback/ProgressView.js';\n\nclass FileUpload {\n constructor(fileModel, options = {}) {\n this.fileModel = fileModel;\n this.options = {\n file: null,\n name: null,\n group: null,\n description: null,\n onProgress: null,\n onComplete: null,\n onError: null,\n showToast: true,\n ...options\n };\n\n // Validation\n if (!this.options.file || !(this.options.file instanceof File)) {\n throw new Error('FileUpload requires a valid File object');\n }\n\n // State management\n this.cancelled = false;\n this.uploadRequest = null;\n this.progressToast = null;\n this.progressView = null;\n this.toastService = null;\n\n // Initialize toast service if needed\n if (this.options.showToast) {\n this.toastService = new ToastService();\n }\n\n // Auto-start upload (Option 3 behavior)\n this.promise = this._startUpload();\n }\n\n /**\n * Main upload orchestration\n * @returns {Promise} Upload promise\n * @private\n */\n async _startUpload() {\n try {\n if (this.options.showToast) {\n this._showProgressToast();\n }\n\n // Stage 1: Initiate upload and get signed URL\n let uploadData;\n try {\n uploadData = await this._initiateUpload();\n } catch (error) {\n throw new Error(`Failed to initiate upload: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Validate upload data\n if (!uploadData || !uploadData.upload_url) {\n throw new Error('Invalid upload response: missing upload URL');\n }\n\n // Stage 2: Upload file to signed URL\n let result;\n try {\n result = await this._performUpload(uploadData.upload_url);\n } catch (error) {\n throw new Error(`File upload failed: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Stage 3: Mark upload as completed\n try {\n await this._completeUpload();\n } catch (error) {\n console.warn('Failed to mark upload as completed:', error);\n // Don't fail the entire upload for completion marking errors\n // The file was successfully uploaded\n }\n\n // Handle success\n this._onComplete(this.fileModel);\n return this.fileModel;\n\n } catch (error) {\n if (error.message !== 'Upload cancelled') {\n this._onError(error);\n }\n throw error;\n }\n }\n\n /**\n * Initiate upload by calling the API to get signed URL\n * @returns {Promise<Object>} Upload initiation data\n * @private\n */\n async _initiateUpload() {\n try {\n const payload = {\n filename: this.options.name || this.options.file.name,\n file_size: this.options.file.size,\n content_type: this.options.file.type,\n };\n\n if (this.options.group) payload.group = this.options.group;\n if (this.options.description) payload.description = this.options.description;\n\n const response = await this.fileModel.rest.POST('/api/fileman/upload/initiate', payload);\n\n if (!response) {\n throw new Error('No response from upload initiation API');\n }\n\n if (!response.data) {\n throw new Error('Upload initiation response missing data');\n }\n\n // Check server response first (prefer server error messages)\n if (!response.data.status) {\n const errorMessage = response.data.error || 'Upload initiation failed';\n throw new Error(errorMessage);\n }\n\n if (!response.data.data) {\n throw new Error('Upload initiation response missing data payload');\n }\n\n // Set model ID for completion step\n if (response.data.data.id) {\n this.fileModel.set('id', response.data.data.id);\n }\n\n return response.data.data; // { id, upload_url }\n\n } catch (error) {\n // Re-throw with more context if it's a generic error\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload initiation. Please check your connection.');\n }\n throw error;\n }\n }\n\n /**\n * Upload file to signed URL with direct XHR control for cancellation\n * @param {string} uploadUrl - Signed upload URL\n * @returns {Promise} Upload result\n * @private\n */\n async _performUpload(uploadUrl) {\n return new Promise((resolve, reject) => {\n // Validate input\n if (!(this.options.file instanceof File)) {\n reject(new Error('Only single File objects are supported'));\n return;\n }\n\n const xhr = new XMLHttpRequest();\n this.uploadRequest = xhr; // Store XHR for cancellation\n\n // Set up progress tracking\n xhr.upload.onprogress = (event) => {\n if (this.cancelled) return;\n\n const progressInfo = {\n progress: event.loaded / event.total,\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100)\n };\n\n this._onProgress(progressInfo);\n };\n\n // Set up response handlers\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({\n data: xhr.response,\n status: xhr.status,\n statusText: xhr.statusText,\n xhr: xhr\n });\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n };\n\n xhr.onerror = () => {\n reject(new Error('Upload failed: Network error'));\n };\n\n xhr.ontimeout = () => {\n reject(new Error('Upload failed: Timeout'));\n };\n\n xhr.onabort = () => {\n reject(new Error('Upload cancelled'));\n };\n\n xhr.ontimeout = () => {\n reject(new Error('Upload timeout - file may be too large or connection too slow'));\n };\n\n // Configure request - use PUT method with raw file data\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', this.options.file.type);\n\n // Set timeout if specified (30 seconds default)\n xhr.timeout = 30000;\n\n // Send the raw file data\n xhr.send(this.options.file);\n });\n }\n\n /**\n * Mark upload as completed in the API\n * @returns {Promise} Completion result\n * @private\n */\n async _completeUpload() {\n try {\n const response = await this.fileModel.save({ action: 'mark_as_completed' });\n\n if (!response) {\n throw new Error('No response from upload completion API');\n }\n\n // Check server response format (prefer server errors over HTTP errors)\n if (response.data && !response.data.status) {\n const errorMessage = response.data.error || 'Failed to mark upload as completed';\n throw new Error(errorMessage);\n }\n\n return response;\n } catch (error) {\n // Re-throw with more context\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload completion. The file may have uploaded successfully.');\n }\n throw error;\n }\n }\n\n /**\n * Handle progress updates\n * @param {Object} progressInfo - Progress information\n * @private\n */\n _onProgress(progressInfo) {\n // Update progress toast if shown\n if (this.progressToast && this.progressToast.updateProgress) {\n this.progressToast.updateProgress(progressInfo);\n }\n\n // Call user-provided progress callback\n if (typeof this.options.onProgress === 'function') {\n this.options.onProgress(progressInfo);\n }\n }\n\n /**\n * Handle successful upload completion\n * @param {Object} result - Upload result\n * @private\n */\n _onComplete(result) {\n // Mark progress view as completed\n if (this.progressView) {\n this.progressView.markCompleted('Upload completed successfully!');\n }\n\n // Auto-hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast:', error);\n }\n }, 2000);\n }\n\n // Call user-provided completion callback\n if (typeof this.options.onComplete === 'function') {\n this.options.onComplete(result);\n }\n }\n\n /**\n * Handle upload errors\n * @param {Error} error - Error object\n * @private\n */\n _onError(error) {\n // Hide progress toast immediately and show error toast\n if (this.progressToast) {\n try {\n this.progressToast.hide();\n } catch (error) {\n console.warn('Error hiding progress toast on error:', error);\n }\n }\n\n // Show error toast with immediate feedback\n if (this.toastService) {\n this.toastService.error(`Upload failed: ${error.message}`);\n }\n\n // Call user-provided error callback\n if (typeof this.options.onError === 'function') {\n this.options.onError(error);\n }\n }\n\n /**\n * Show progress toast with ProgressView component\n * @private\n */\n _showProgressToast() {\n // Create progress view with file information\n this.progressView = new ProgressView({\n filename: this.options.name || this.options.file.name,\n filesize: this.options.file.size,\n showCancel: true,\n onCancel: () => this.cancel()\n });\n\n // Show progress view in toast\n this.progressToast = this.toastService.showView(this.progressView, 'info', {\n title: 'File Upload',\n autohide: false,\n dismissible: false\n });\n }\n\n /**\n * Cancel the upload\n * @returns {boolean} True if cancelled, false if already completed\n */\n cancel() {\n if (this.cancelled) {\n return false;\n }\n\n this.cancelled = true;\n\n // Cancel the upload request if in progress\n if (this.uploadRequest && typeof this.uploadRequest.abort === 'function') {\n this.uploadRequest.abort();\n }\n\n // Mark progress view as cancelled\n if (this.progressView) {\n this.progressView.markCancelled();\n }\n\n // Hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast on cancel:', error);\n }\n }, 1500);\n }\n\n return true;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Promise interface - then\n * @param {function} onSuccess - Success handler\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n then(onSuccess, onError) {\n return this.promise.then(onSuccess, onError);\n }\n\n /**\n * Promise interface - catch\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n catch(onError) {\n return this.promise.catch(onError);\n }\n\n /**\n * Promise interface - finally\n * @param {function} onFinally - Finally handler\n * @returns {Promise} Promise chain\n */\n finally(onFinally) {\n return this.promise.finally(onFinally);\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.options.file.name,\n size: this.options.file.size,\n type: this.options.file.type,\n cancelled: this.cancelled,\n group: this.options.group,\n description: this.options.description\n };\n }\n}\n\nexport default FileUpload;\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport FileUpload from '@core/services/FileUpload.js';\nimport {UserList} from '@core/models/User.js';\nimport {GroupList} from '@core/models/Group.js';\n/* =========================\n * FileManager\n * ========================= */\nclass FileManager extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/manager',\n });\n }\n}\n\nclass FileManagerList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: FileManager,\n endpoint: '/api/fileman/manager',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileManagerForms = {\n create: {\n title: 'Add Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n value: \"s3://BUCKET_NAME/OPTION_FOLDER\",\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n },\n ],\n },\n\n edit: {\n title: 'Edit Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'allowed_origins',\n type: 'text',\n label: 'Domains Who Can Upload',\n cols: 12,\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n }\n ],\n },\n\n owners: {\n fields: [\n {\n type: 'collection',\n name: 'group',\n label: 'Group (Owner)',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n {\n type: 'collection',\n name: 'user',\n label: 'User (Owner)',\n Collection: UserList, // Collection class\n labelField: 'display_name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search users...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n ],\n },\n\n credentials: {\n fields: [\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n ]\n }\n};\n\n/* =========================\n * File\n * ========================= */\nclass File extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/file',\n });\n }\n\n isImage() {\n return this.get(\"category\") === 'image';\n }\n\n /**\n * Upload file with progress tracking and UI integration\n * Returns a FileUpload instance with promise interface and cancellation support\n *\n * @param {object} options - Upload configuration\n * @param {File} options.file - File object to upload\n * @param {string} options.name - Custom filename (optional)\n * @param {string} options.group - File group/category (optional)\n * @param {string} options.description - File description (optional)\n * @param {function} options.onProgress - Progress callback ({ progress, loaded, total, percentage })\n * @param {function} options.onComplete - Success callback\n * @param {function} options.onError - Error callback\n * @param {boolean} options.showToast - Show progress toast (default: true)\n * @returns {FileUpload} Upload instance with promise interface\n */\n upload(options = {}) {\n return new FileUpload(this, options);\n }\n}\n\nclass FileList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: File,\n endpoint: '/api/fileman/file',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileForms = {\n create: {\n title: 'Add File',\n fields: [\n\n ],\n },\n\n edit: {\n title: 'Edit File Backend',\n fields: [\n\n ],\n },\n};\n\nexport {\n FileManager,\n FileManagerList,\n FileManagerForms,\n File,\n FileList,\n FileForms,\n};\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * IncidentEvent\n * ========================= */\n class IncidentStats extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/stats',\n requiresId: false\n });\n }\n }\n\n\nclass IncidentEvent extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event',\n });\n }\n}\n\nclass IncidentEventList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentEvent,\n endpoint: '/api/incident/event',\n size: 10,\n ...options,\n });\n }\n}\n\nconst IncidentEventForms = {\n edit: {\n title: 'Edit Incident Event',\n fields: [\n {\n name: 'category',\n type: 'select',\n label: 'Category',\n placeholder: 'Select category',\n options: () => Incident.COMPONENTS,\n editable: true,\n force_top: true,\n cols: 6,\n },\n {\n name: 'incident',\n type: 'text',\n label: 'Incident',\n placeholder: 'Enter Incident ID',\n cols: 6,\n },\n {\n name: 'description',\n type: 'textarea',\n label: 'Description',\n placeholder: 'Enter Description',\n cols: 12,\n },\n {\n name: 'details',\n type: 'textarea',\n label: 'Details',\n placeholder: 'Enter Details',\n cols: 12,\n },\n {\n name: 'component',\n type: 'text',\n label: 'Component',\n placeholder: 'Enter Component',\n cols: 8,\n },\n {\n name: 'component_id',\n type: 'text',\n label: 'Component ID',\n placeholder: 'Enter Component ID',\n cols: 4,\n },\n ],\n },\n};\n\n/* =========================\n * Incident\n * ========================= */\nclass Incident extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/incident',\n });\n }\n}\n\nclass IncidentList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Incident,\n endpoint: '/api/incident/incident',\n size: 10,\n ...options,\n });\n }\n}\n\nconst IncidentForms = {\n create: {\n title: 'Create Incident',\n fields: [\n {\n type: 'tabset',\n name: 'settingsTabs',\n tabs: [\n {\n label: 'General',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Title',\n required: true,\n columns: 12\n },\n {\n name: 'details',\n type: 'textarea',\n label: 'Details',\n required: true,\n columns: 12\n },\n\n ]\n },\n {\n label: 'Advanced',\n fields: [\n {\n name: 'priority',\n type: 'select',\n label: 'Priority',\n options: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],\n value: 5,\n columns: 6\n },\n {\n name: 'status',\n type: 'select',\n label: 'Status',\n value: 'open',\n options: ['open', 'investigating', 'resolved', 'closed', \"paused\", \"ignored\"],\n columns: 6\n },\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n value: 'manual',\n columns: 6\n },\n ]\n },\n {\n label: 'Metadata',\n fields: [\n {\n name: 'metadata',\n type: 'json',\n label: 'Metadata',\n value: { \"example\": \"hello world\" },\n rows: 15,\n columns: 12\n }\n ]\n }\n ]\n },\n\n ]\n },\n edit: {\n title: 'Edit Incident',\n fields: [\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n cols: 6,\n },\n {\n name: 'state',\n type: 'select',\n label: 'State',\n placeholder: 'Select State',\n options: ['new', 'opened', 'paused', 'ignore', 'resolved'],\n cols: 3,\n },\n {\n name: 'priority',\n type: 'text',\n label: 'Priority',\n },\n {\n name: 'details',\n type: 'textarea',\n label: 'Description',\n placeholder: 'Enter Name',\n cols: 12,\n },\n {\n name: 'model_name',\n type: 'text',\n label: 'Model',\n placeholder: 'Enter Model',\n cols: 8,\n },\n {\n name: 'model_id',\n type: 'text',\n label: 'Model ID',\n placeholder: 'Enter Model ID',\n cols: 4,\n },\n ],\n },\n};\n\n/* =========================\n * RuleSet / Rule\n * ========================= */\nclass IncidentRuleSet extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset',\n });\n }\n}\n\nclass IncidentRuleSetList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentRuleSet,\n endpoint: '/api/incident/event/ruleset',\n size: 10,\n ...options,\n });\n }\n}\n\nclass IncidentRule extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset/rule',\n });\n }\n}\n\nclass IncidentRuleList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentRule,\n endpoint: '/api/incident/event/ruleset/rule',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * IncidentHistory\n * ========================= */\nclass IncidentHistory extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/incident/history',\n });\n }\n}\n\nclass IncidentHistoryList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentHistory,\n endpoint: '/api/incident/incident/history',\n size: 10,\n ...options,\n });\n }\n}\n\n\nexport {\n IncidentEvent,\n IncidentEventList,\n IncidentEventForms,\n Incident,\n IncidentList,\n IncidentForms,\n IncidentRuleSet,\n IncidentRuleSetList,\n IncidentRule,\n IncidentRuleList,\n IncidentHistory,\n IncidentHistoryList\n};\n\n/* =========================\n * RuleSet\n * ========================= */\n\n// Bundle By Options\nconst BundleByOptions = [\n { value: 0, label: 'No Bundling' },\n { value: 1, label: 'By Hostname' },\n { value: 2, label: 'By Model Name' },\n { value: 3, label: 'By Model Name + ID' },\n { value: 4, label: 'By Source IP' },\n { value: 5, label: 'By Hostname + Model Name' },\n { value: 6, label: 'By Hostname + Model Name + ID' },\n { value: 7, label: 'By Source IP + Model Name' },\n { value: 8, label: 'By Source IP + Model Name + ID' },\n { value: 9, label: 'By Source IP + Hostname' }\n];\n\n// Match By Options\nconst MatchByOptions = [\n { value: 0, label: 'ALL (must match all rules)' },\n { value: 1, label: 'ANY (match any rule)' }\n];\n\n// Comparator Options\nconst ComparatorOptions = [\n { value: '==', label: 'Equal (==)' },\n { value: 'eq', label: 'Equal (eq)' },\n { value: '>', label: 'Greater Than (>)' },\n { value: '>=', label: 'Greater Than or Equal (>=)' },\n { value: '<', label: 'Less Than (<)' },\n { value: '<=', label: 'Less Than or Equal (<=)' },\n { value: 'contains', label: 'Contains' },\n { value: 'regex', label: 'Regular Expression' }\n];\n\n// Value Type Options\nconst ValueTypeOptions = [\n { value: 'str', label: 'String' },\n { value: 'int', label: 'Integer' },\n { value: 'float', label: 'Float' },\n { value: 'bool', label: 'Boolean' }\n];\n\n// Common Event Fields for ComboInput\nconst CommonEventFields = [\n { value: 'level', label: 'Level', description: 'Event level (error, warning, info, debug)', meta: { type: 'str' } },\n { value: 'source_ip', label: 'Source IP Address', description: 'IP address of the event source', meta: { type: 'str' } },\n { value: 'rule_id', label: 'Rule ID', description: 'Numeric rule identifier', meta: { type: 'int' } },\n { value: 'hostname', label: 'Hostname', description: 'Hostname where event occurred', meta: { type: 'str' } },\n { value: 'component', label: 'Component', description: 'System component name', meta: { type: 'str' } },\n { value: 'component_id', label: 'Component ID', description: 'Component identifier', meta: { type: 'str' } },\n { value: 'category', label: 'Category', description: 'Event category (ossec, auth, api_error, etc.)', meta: { type: 'str' } },\n { value: 'description', label: 'Description', description: 'Event description text', meta: { type: 'str' } },\n { value: 'details', label: 'Details', description: 'Additional event details', meta: { type: 'str' } },\n { value: 'alert_id', label: 'Alert ID', description: 'Numeric alert identifier', meta: { type: 'int' } },\n { value: 'severity', label: 'Severity', description: 'Numeric severity level', meta: { type: 'int' } },\n { value: 'user', label: 'User', description: 'Username associated with event', meta: { type: 'str' } },\n { value: 'action', label: 'Action', description: 'Action that triggered the event', meta: { type: 'str' } },\n { value: 'status', label: 'Status', description: 'Status value or code', meta: { type: 'str' } },\n { value: 'status_code', label: 'Status Code', description: 'Numeric status code (e.g., HTTP status)', meta: { type: 'int' } },\n { value: 'message', label: 'Message', description: 'Event message text', meta: { type: 'str' } },\n { value: 'path', label: 'Path', description: 'File path or URL path', meta: { type: 'str' } },\n { value: 'title', label: 'Title', description: 'OSSEC Title', meta: { type: 'str' } },\n { value: 'country_code', label: 'Country Code', description: 'Country code associated with event', meta: { type: 'str' } },\n { value: 'region', label: 'Region', description: 'Region associated with event', meta: { type: 'str' } },\n { value: 'city', label: 'City', description: 'City associated with event', meta: { type: 'str' } },\n { value: 'http_user_agent', label: 'HTTP User Agent', description: 'User agent string associated with event', meta: { type: 'str' } },\n { value: 'request_path', label: 'Request Path', description: 'Request path associated with event', meta: { type: 'str' } },\n { value: 'method', label: 'Method', description: 'HTTP method or function name', meta: { type: 'str' } }\n];\n\nclass RuleSet extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset',\n });\n }\n}\n\nclass RuleSetList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: RuleSet,\n endpoint: '/api/incident/event/ruleset',\n ...options,\n });\n }\n}\n\nconst RuleSetForms = {\n create: {\n title: 'Create RuleSet',\n size: 'lg',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter ruleset name',\n cols: 12\n },\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n required: true,\n placeholder: 'e.g., ossec, auth, api_error',\n cols: 6\n },\n {\n name: 'priority',\n type: 'number',\n label: 'Priority',\n value: 10,\n required: true,\n cols: 6\n },\n {\n name: 'match_by',\n type: 'select',\n label: 'Match Logic',\n value: 0,\n options: MatchByOptions,\n cols: 12\n },\n {\n name: 'bundle_by',\n type: 'select',\n label: 'Bundle By',\n value: 0,\n options: BundleByOptions,\n cols: 12\n },\n {\n name: 'bundle_minutes',\n type: 'number',\n label: 'Bundle Minutes',\n value: 10,\n placeholder: 'Time window for bundling',\n cols: 12\n },\n {\n name: 'handler',\n type: 'text',\n label: 'Handler',\n placeholder: 'e.g., email://user@company.com, task://name',\n cols: 12\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n value: true,\n cols: 6\n },\n {\n name: 'bundle_by_rule_set',\n type: 'switch',\n label: 'Bundle by Rule Set',\n value: true,\n cols: 6\n }\n ]\n },\n edit: {\n title: 'Edit RuleSet',\n size: 'lg',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter ruleset name',\n cols: 12\n },\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n required: true,\n placeholder: 'e.g., ossec, auth, api_error',\n cols: 6\n },\n {\n name: 'priority',\n type: 'number',\n label: 'Priority',\n required: true,\n cols: 6\n },\n {\n name: 'match_by',\n type: 'select',\n label: 'Match Logic',\n options: MatchByOptions,\n cols: 12\n },\n {\n name: 'bundle_by',\n type: 'select',\n label: 'Bundle By',\n options: BundleByOptions,\n cols: 12\n },\n {\n name: 'bundle_minutes',\n type: 'select',\n label: 'Bundle Minutes',\n placeholder: 'Time window for bundling',\n \"options\": [\n {\"value\": 0, \"label\": \"Disabled - don't bundle by time\"},\n {\"value\": 5, \"label\": \"5 minutes\"},\n {\"value\": 10, \"label\": \"10 minutes\"},\n {\"value\": 15, \"label\": \"15 minutes\"},\n {\"value\": 30, \"label\": \"30 minutes\"},\n {\"value\": 60, \"label\": \"1 hour\"},\n {\"value\": 120, \"label\": \"2 hours\"},\n {\"value\": 360, \"label\": \"6 hours\"},\n {\"value\": 720, \"label\": \"12 hours\"},\n {\"value\": 1440, \"label\": \"1 day\"},\n {\"value\": null, \"label\": \"No limit - bundle forever\"}\n ],\n cols: 12\n },\n {\n name: 'handler',\n type: 'text',\n label: 'Handler',\n placeholder: 'e.g., email://user@company.com, task://name',\n cols: 12\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n value: true,\n cols: 6\n },\n {\n name: 'bundle_by_rule_set',\n type: 'switch',\n label: 'Bundle by Rule Set',\n value: true,\n cols: 6\n }\n ]\n }\n};\n\n/* =========================\n * Rule\n * ========================= */\nclass Rule extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset/rule',\n });\n }\n}\n\nclass RuleList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Rule,\n endpoint: '/api/incident/event/ruleset/rule',\n ...options,\n });\n }\n}\n\nconst RuleForms = {\n create: {\n title: 'Create Rule',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter rule name',\n cols: 12\n },\n {\n name: 'field_name',\n type: 'combo',\n label: 'Field Name',\n required: true,\n placeholder: 'Select or enter field name...',\n options: CommonEventFields,\n allowCustom: true,\n showDescription: true,\n help: 'Select a common field or type a custom field name',\n cols: 12\n },\n {\n name: 'comparator',\n type: 'select',\n label: 'Comparator',\n required: true,\n options: ComparatorOptions,\n cols: 6\n },\n {\n name: 'value_type',\n type: 'select',\n label: 'Value Type',\n required: true,\n options: ValueTypeOptions,\n value: 'str',\n cols: 6\n },\n {\n name: 'value',\n type: 'text',\n label: 'Value',\n required: true,\n placeholder: 'Enter comparison value',\n cols: 12\n }\n ]\n },\n edit: {\n title: 'Edit Rule',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter rule name',\n cols: 12\n },\n {\n name: 'field_name',\n type: 'combo',\n label: 'Field Name',\n required: true,\n placeholder: 'Select or enter field name...',\n options: CommonEventFields,\n allowCustom: true,\n showDescription: true,\n help: 'Select a common field or type a custom field name',\n cols: 12\n },\n {\n name: 'comparator',\n type: 'select',\n label: 'Comparator',\n required: true,\n options: ComparatorOptions,\n cols: 6\n },\n {\n name: 'value_type',\n type: 'select',\n label: 'Value Type',\n required: true,\n options: ValueTypeOptions,\n cols: 6\n },\n {\n name: 'value',\n type: 'text',\n label: 'Value',\n required: true,\n placeholder: 'Enter comparison value',\n cols: 12\n }\n ]\n }\n};\n\n// Attach forms to models\nRuleSet.EDIT_FORM = RuleSetForms.edit;\nRuleSet.ADD_FORM = RuleSetForms.create;\nRuleSet.CREATE_FORM = RuleSetForms.create; // Alias for compatibility\nRuleSet.BundleByOptions = BundleByOptions;\nRuleSet.MatchByOptions = MatchByOptions;\n\nRule.EDIT_FORM = RuleForms.edit;\nRule.ADD_FORM = RuleForms.create;\nRule.CREATE_FORM = RuleForms.create; // Alias for compatibility\nRule.ComparatorOptions = ComparatorOptions;\nRule.ValueTypeOptions = ValueTypeOptions;\n\nexport {\n RuleSet,\n RuleSetList,\n RuleSetForms,\n Rule,\n RuleList,\n RuleForms,\n IncidentStats,\n BundleByOptions,\n MatchByOptions,\n ComparatorOptions,\n ValueTypeOptions,\n CommonEventFields\n};\n","/**\n * Job Model - Async Job Engine management\n * Supports object-oriented POST_SAVE_ACTIONS pattern from Jobs API\n */\n\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Job extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/job'\n });\n }\n\n // Object-oriented actions using POST_SAVE_ACTIONS pattern\n async cancel() {\n const response = await this.save({ cancel_request: true });\n if (response.success && response.data.status) {\n // Update local status if successful\n this.set('cancel_requested', true);\n }\n return response;\n }\n\n async retry(delay = null) {\n const data = delay ?\n { retry_request: { retry: true, delay } } :\n { retry_request: true };\n\n const response = await this.save(data);\n if (response.success && response.data.status && response.data.new_job_id) {\n // Return new job ID if created\n return {\n ...response,\n newJobId: response.data.new_job_id,\n originalJobId: response.data.original_job_id\n };\n }\n return response;\n }\n\n async getDetailedStatus() {\n const response = await this.save({ get_status: true });\n if (response.success && response.data.status) {\n // Update local data with detailed status\n this.set(response.data.data);\n }\n return response;\n }\n\n async cloneJob(newPayload = {}) {\n const publishData = {\n publish_job: {\n payload: newPayload,\n ...newPayload\n }\n };\n\n const response = await this.save(publishData);\n if (response.success && response.data.status && response.data.job_id) {\n return {\n ...response,\n newJobId: response.data.job_id,\n templateJobId: response.data.template_job_id\n };\n }\n return response;\n }\n\n // Status helper methods\n isActive() {\n const status = this.get('status');\n return ['pending', 'running'].includes(status);\n }\n\n isTerminal() {\n const status = this.get('status');\n return ['completed', 'failed', 'canceled', 'expired'].includes(status);\n }\n\n canRetry() {\n const status = this.get('status');\n return ['failed', 'canceled', 'expired'].includes(status) && this.get('is_retriable') !== false;\n }\n\n canCancel() {\n const status = this.get('status');\n return ['pending', 'running'].includes(status) && !this.get('cancel_requested');\n }\n\n // Get status badge class for UI\n getStatusBadgeClass() {\n const status = this.get('status');\n const classes = {\n 'pending': 'bg-primary',\n 'running': 'bg-success',\n 'completed': 'bg-info',\n 'failed': 'bg-danger',\n 'canceled': 'bg-secondary',\n 'expired': 'bg-warning'\n };\n return classes[status] || 'bg-secondary';\n }\n\n // Get status icon\n getStatusIcon() {\n const status = this.get('status');\n const icons = {\n 'pending': 'bi-hourglass',\n 'running': 'bi-arrow-repeat',\n 'completed': 'bi-check-circle',\n 'failed': 'bi-x-octagon',\n 'canceled': 'bi-x-circle',\n 'expired': 'bi-clock'\n };\n return icons[status] || 'bi-question-circle';\n }\n\n // Get recent events for timeline\n getEvents() {\n return this.get('recent_events') || [];\n }\n\n // Get formatted duration\n getFormattedDuration() {\n const duration = this.get('duration_ms');\n if (!duration || duration === 0) return 'N/A';\n\n if (duration < 1000) return `${duration}ms`;\n if (duration < 60000) return `${(duration / 1000).toFixed(1)}s`;\n if (duration < 3600000) return `${(duration / 60000).toFixed(1)}m`;\n return `${(duration / 3600000).toFixed(1)}h`;\n }\n\n // Get queue position if available\n getQueuePosition() {\n return this.get('queue_position');\n }\n\n // Check if job has expired\n hasExpired() {\n const expiresAt = this.get('expires_at');\n if (!expiresAt) return false;\n return new Date(expiresAt) < new Date();\n }\n\n // Get runner information\n getRunnerId() {\n return this.get('runner_id');\n }\n\n // Get payload data\n getPayload() {\n return this.get('payload') || {};\n }\n\n // Get metadata\n getMetadata() {\n return this.get('metadata') || {};\n }\n}\n\nclass JobList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Job,\n endpoint: '/api/jobs/job',\n ...options\n });\n }\n\n // Filter by status\n async fetchByStatus(status, params = {}) {\n return this.fetch({ status, ...params });\n }\n\n // Filter by channel\n async fetchByChannel(channel, params = {}) {\n return this.fetch({ channel, ...params });\n }\n\n // Get pending jobs\n async fetchPending(params = {}) {\n return this.fetchByStatus('pending', params);\n }\n\n // Get running jobs\n async fetchRunning(params = {}) {\n return this.fetchByStatus('running', params);\n }\n\n // Get completed jobs\n async fetchCompleted(params = {}) {\n return this.fetchByStatus('completed', params);\n }\n\n // Get failed jobs\n async fetchFailed(params = {}) {\n return this.fetchByStatus('failed', params);\n }\n\n // Get scheduled jobs (those with run_at)\n async fetchScheduled(params = {}) {\n return this.fetch({\n scheduled: true, // This may need to be adjusted based on actual API\n ...params\n });\n }\n}\n\n// Form configurations for job management\nconst JobForms = {\n publish: {\n title: 'Publish New Job',\n fields: [\n {\n name: 'func',\n type: 'text',\n label: 'Function',\n required: true,\n placeholder: 'myapp.jobs.send_email',\n help: 'Module path to job function'\n },\n {\n name: 'channel',\n type: 'text',\n label: 'Channel',\n value: 'default',\n help: 'Queue channel (default: \"default\")'\n },\n {\n name: 'payload',\n type: 'textarea',\n label: 'Payload (JSON)',\n required: true,\n rows: 8,\n placeholder: '{\\n \"key\": \"value\"\\n}',\n help: 'JSON data passed to the job function'\n },\n {\n name: 'delay',\n type: 'number',\n label: 'Delay (seconds)',\n min: 0,\n help: 'Delay execution by specified seconds'\n },\n {\n name: 'run_at',\n type: 'datetime-local',\n label: 'Run At',\n help: 'Schedule for specific date/time'\n },\n {\n name: 'max_retries',\n type: 'number',\n label: 'Max Retries',\n value: 3,\n min: 0,\n max: 10\n },\n {\n name: 'expires_in',\n type: 'number',\n label: 'Expires In (seconds)',\n value: 900,\n min: 60,\n help: 'Job will expire if not completed in this time'\n },\n {\n name: 'broadcast',\n type: 'switch',\n label: 'Broadcast to All Workers',\n help: 'Execute on all available workers'\n }\n ]\n },\n\n retry: {\n title: 'Retry Job',\n fields: [\n {\n name: 'delay',\n type: 'number',\n label: 'Delay (seconds)',\n value: 0,\n min: 0,\n help: 'Delay before retry (0 = immediate)'\n }\n ]\n },\n\n clone: {\n title: 'Clone Job',\n fields: [\n {\n name: 'channel',\n type: 'text',\n label: 'Channel',\n help: 'Override channel for cloned job'\n },\n {\n name: 'payload',\n type: 'textarea',\n label: 'Modified Payload (JSON)',\n rows: 8,\n help: 'Modified payload for cloned job'\n },\n {\n name: 'delay',\n type: 'number',\n label: 'Delay (seconds)',\n min: 0\n }\n ]\n }\n};\n\n// Static method to publish new job directly\nJob.publish = async function(jobData) {\n return await rest.POST('/api/jobs/publish', jobData);\n};\n\n// Static method to get system stats\nJob.getStats = async function() {\n const response = await rest.GET('/api/jobs/stats');\n return response.success ? response.data : null;\n};\n\n// Static method to get system health\nJob.getHealth = async function(channel = null) {\n const endpoint = channel ? `/api/jobs/health/${channel}` : '/api/jobs/health';\n const response = rest.GET(endpoint);\n return response.success ? response.data : null;\n};\n\n// Static method to run test job\nJob.test = async function() {\n return await rest.POST('/api/jobs/test', {});\n};\n\n// Static method to run test jobs\nJob.tests = async function() {\n return await rest.POST('/api/jobs/tests', {});\n};\n\nJob.clearStuck = async function(channel = null) {\n const payload = channel ? { channel } : {};\n return await rest.POST('/api/jobs/control/clear-stuck', payload);\n};\n\nJob.clearChannel = async function(channel) {\n return await rest.POST('/api/jobs/control/clear-queue', { channel, confirm:\"yes\" });\n};\n\nJob.cleanConsumers = async function() {\n return await rest.POST('/api/jobs/control/cleanup-consumers');\n};\n\nJob.purgeJobs = async function(days_old) {\n return await rest.POST('/api/jobs/control/purge', { days_old });\n};\n\n\nclass JobLog extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/logs'\n });\n }\n}\n\nclass JobLogList extends Collection {\n constructor(options = {}) {\n super({\n endpoint: '/api/jobs/logs',\n model: JobLog,\n ...options\n });\n }\n}\n\n\nclass JobEvent extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/event'\n });\n }\n}\n\nclass JobEventList extends Collection {\n constructor(options = {}) {\n super({\n endpoint: '/api/jobs/event',\n model: JobEvent,\n ...options\n });\n }\n}\n\nclass JobsEngineStats extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/stats',\n requiresId: false\n });\n }\n}\n\nexport {\n Job, JobList, JobForms,\n JobLog, JobLogList, JobEvent,\n JobEventList, JobsEngineStats };\n","/**\n * JobRunner Model - Job Engine Runner/Worker management\n * Manages worker processes that execute jobs\n */\n\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\nclass JobRunner extends Model {\n constructor(data = {}) {\n // Map runner_id to id for proper Model handling\n if (data.runner_id && !data.id) {\n data.id = data.runner_id;\n }\n \n super(data, {\n endpoint: '/api/jobs/runners',\n idAttribute: 'runner_id' // Tell the model to use runner_id as the ID field\n });\n }\n\n // Ping this runner to test connectivity\n async ping(timeout = 2.0) {\n const app = this.getApp();\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n\n const response = await app.rest.POST('/api/jobs/runners/ping', {\n runner_id: this.get('runner_id'),\n timeout: timeout\n });\n\n if (response.success && response.data.status) {\n // Update ping status\n this.set('last_heartbeat', new Date().toISOString());\n this.set('ping_status', response.data.ping_status || 'success');\n }\n\n return response;\n }\n\n // Shutdown this runner gracefully or forcefully\n async shutdown(graceful = true) {\n const app = this.getApp();\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n\n const response = await app.rest.POST('/api/jobs/runners/shutdown', {\n runner_id: this.get('runner_id'),\n graceful: graceful\n });\n\n if (response.success && response.data.status) {\n // Update status to shutting down (alive becomes false)\n this.set('alive', false);\n }\n\n return response;\n }\n\n // Get channels handled by this runner\n getChannels() {\n return this.get('channels') || [];\n }\n\n // Get runner status with appropriate styling based on alive field\n getStatusBadgeClass() {\n const alive = this.get('alive');\n return alive ? 'bg-success' : 'bg-danger';\n }\n\n // Get status icon based on alive field\n getStatusIcon() {\n const alive = this.get('alive');\n return alive ? 'bi-check-circle-fill' : 'bi-x-octagon-fill';\n }\n\n // Check if runner is active (alive)\n isActive() {\n return this.get('alive') === true;\n }\n\n // Check if runner is healthy (active and recent heartbeat)\n isHealthy() {\n if (!this.isActive()) return false;\n \n const lastHeartbeat = this.get('last_heartbeat');\n if (!lastHeartbeat) return false;\n \n // Consider healthy if heartbeat within last 2 minutes\n const heartbeatAge = (Date.now() - new Date(lastHeartbeat).getTime()) / 1000;\n return heartbeatAge < 120;\n }\n\n // Get formatted heartbeat age\n getFormattedHeartbeatAge() {\n const lastHeartbeat = this.get('last_heartbeat');\n if (!lastHeartbeat) return 'Never';\n \n const heartbeatAge = (Date.now() - new Date(lastHeartbeat).getTime()) / 1000;\n \n if (heartbeatAge < 60) return `${Math.round(heartbeatAge)}s ago`;\n if (heartbeatAge < 3600) return `${Math.round(heartbeatAge / 60)}m ago`;\n return `${Math.round(heartbeatAge / 3600)}h ago`;\n }\n\n // Get current utilization (from jobs processed vs capacity)\n getUtilization() {\n const processed = this.get('jobs_processed') || 0;\n const failed = this.get('jobs_failed') || 0;\n const total = processed + failed;\n // Since we don't have max capacity from API, return based on recent activity\n return total > 10 ? 100 : Math.min(total * 10, 100);\n }\n\n // Get formatted uptime based on started timestamp\n getFormattedUptime() {\n const started = this.get('started');\n if (!started) return 'Unknown';\n \n const startTime = new Date(started);\n const now = new Date();\n const diffMs = now - startTime;\n const diffSeconds = Math.floor(diffMs / 1000);\n \n if (diffSeconds < 60) return `${diffSeconds}s`;\n if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m`;\n if (diffSeconds < 86400) return `${Math.floor(diffSeconds / 3600)}h`;\n return `${Math.floor(diffSeconds / 86400)}d`;\n }\n\n // Get worker capacity info\n getWorkerInfo() {\n const processed = this.get('jobs_processed') || 0;\n const failed = this.get('jobs_failed') || 0;\n return {\n processed,\n failed,\n total: processed + failed,\n alive: this.get('alive'),\n utilization: this.getUtilization()\n };\n }\n\n // Get display name for runner\n getDisplayName() {\n const runnerId = this.get('runner_id');\n if (!runnerId) return 'Unknown Runner';\n \n // Extract hostname-like portion if it exists\n const parts = runnerId.split('-');\n return parts.length > 1 ? parts[0] : runnerId;\n }\n\n // Check if runner can be controlled\n canControl() {\n return this.get('alive') === true;\n }\n}\n\nclass JobRunnerList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: JobRunner,\n endpoint: '/api/jobs/runners',\n ...options\n });\n }\n\n // Get only active runners\n getActive() {\n return this.where(runner => runner.isActive());\n }\n\n // Get runners by channel\n getByChannel(channel) {\n return this.where(runner => runner.getChannels().includes(channel));\n }\n\n // Get healthy runners\n getHealthy() {\n return this.where(runner => runner.isHealthy());\n }\n\n // Get total jobs processed across all runners\n getTotalProcessed() {\n return this.models.reduce((total, runner) => {\n return total + (runner.get('jobs_processed') || 0);\n }, 0);\n }\n\n // Get total jobs failed across all runners\n getTotalFailed() {\n return this.models.reduce((total, runner) => {\n return total + (runner.get('jobs_failed') || 0);\n }, 0);\n }\n\n // Get overall system health percentage (alive runners / total runners)\n getSystemHealth() {\n if (this.models.length === 0) return 0;\n const aliveRunners = this.models.filter(runner => runner.get('alive')).length;\n return Math.round((aliveRunners / this.models.length) * 100);\n }\n\n // Get unique channels across all runners\n getAllChannels() {\n const channels = new Set();\n this.models.forEach(runner => {\n runner.getChannels().forEach(channel => channels.add(channel));\n });\n return Array.from(channels).sort();\n }\n}\n\n// Static methods for system-wide runner management\nJobRunner.ping = async function(runnerId, timeout = 2.0) {\n const app = typeof window !== 'undefined' && window.app;\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n \n return await app.rest.POST('/api/jobs/runners/ping', {\n runner_id: runnerId,\n timeout\n });\n};\n\nJobRunner.shutdown = async function(runnerId, graceful = true) {\n const app = typeof window !== 'undefined' && window.app;\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n \n return await app.rest.POST('/api/jobs/runners/shutdown', {\n runner_id: runnerId,\n graceful\n });\n};\n\nJobRunner.broadcast = async function(command, data = {}, timeout = 2.0) {\n const app = typeof window !== 'undefined' && window.app;\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n \n return await app.rest.POST('/api/jobs/runners/broadcast', {\n command,\n data,\n timeout\n });\n};\n\n// Broadcast commands\nJobRunner.broadcastStatus = async function(timeout = 2.0) {\n return JobRunner.broadcast('status', {}, timeout);\n};\n\nJobRunner.broadcastShutdown = async function(timeout = 2.0) {\n return JobRunner.broadcast('shutdown', {}, timeout);\n};\n\nJobRunner.broadcastPause = async function(timeout = 2.0) {\n return JobRunner.broadcast('pause', {}, timeout);\n};\n\nJobRunner.broadcastResume = async function(timeout = 2.0) {\n return JobRunner.broadcast('resume', {}, timeout);\n};\n\nJobRunner.broadcastReload = async function(timeout = 2.0) {\n return JobRunner.broadcast('reload', {}, timeout);\n};\n\nconst JobRunnerForms = {\n broadcast: {\n title: 'Broadcast Command',\n fields: [\n {\n name: 'command',\n type: 'select',\n label: 'Command',\n required: true,\n options: [\n { value: 'status', label: 'Status Check' },\n { value: 'pause', label: 'Pause Processing' },\n { value: 'resume', label: 'Resume Processing' },\n { value: 'reload', label: 'Reload Configuration' },\n { value: 'shutdown', label: 'Shutdown All Runners' }\n ],\n help: 'Command to send to all runners'\n },\n {\n name: 'timeout',\n type: 'number',\n label: 'Timeout (seconds)',\n value: 2.0,\n min: 0.5,\n max: 10.0,\n step: 0.5,\n help: 'How long to wait for responses'\n }\n ]\n }\n};\n\nexport { JobRunner, JobRunnerList, JobRunnerForms };","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Log extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/logs',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass LogList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Log,\n endpoint: '/api/logs',\n size: 10,\n ...options,\n });\n }\n}\n\nexport { Log, LogList };\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Member extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group/member',\n });\n }\n\n hasPermission(permission) {\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n return permissions[permission] == true;\n }\n\n async fetchForGroup(groupId) {\n return await this.fetch({ url: `/api/group/${groupId}/member` });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass MemberList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Member,\n endpoint: '/api/group/member',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst MemberForms = {\n\n edit: {\n title: 'Edit Membership',\n fields: [\n {\n name: 'user.display_name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name'\n },\n {\n name: 'metadata.role',\n type: 'text',\n label: 'Role',\n placeholder: 'Enter role'\n },\n {\n name: `is_active`,\n type: 'switch',\n label: \"Is Enabled\",\n columns: 12\n }\n ]\n }\n};\n\n\nMember.EDIT_FORM = MemberForms.edit;\nMember.ADD_FORM = MemberForms.create;\n\n\nexport { Member, MemberList, MemberForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\nclass MetricsPermission extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/metrics/permissions',\n id_key: 'account'\n });\n }\n}\n\nclass MetricsPermissionList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: MetricsPermission,\n endpoint: '/api/metrics/permissions',\n ...options,\n });\n }\n\n}\n\nconst MetricsForms = {\n edit: {\n title: 'Edit Metrics Permissions',\n fields: [\n { name: 'account', type: 'text', label: 'Account', columns:12 },\n { name: 'view_permissions', type: 'tags', label: 'View Permissions', help: 'Enter permissions or \"public\"', columns:12 },\n { name: 'write_permissions', type: 'tags', label: 'Write Permissions', help: 'Enter permissions', columns:12 },\n ]\n }\n};\n\nexport { MetricsPermission, MetricsPermissionList, MetricsForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport { GroupList } from './Group.js';\n\n// =========================\n// PushDevice\n// =========================\nclass PushDevice extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push' });\n }\n}\n\nclass PushDeviceList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushDevice, endpoint: '/api/account/devices/push', ...options });\n }\n}\n\n// =========================\n// PushTemplate\n// =========================\nclass PushTemplate extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push/templates' });\n }\n}\n\nclass PushTemplateList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushTemplate, endpoint: '/api/account/devices/push/templates', ...options });\n }\n}\n\n// =========================\n// PushConfig\n// =========================\nclass PushConfig extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push/config' });\n }\n}\n\nclass PushConfigList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushConfig, endpoint: '/api/account/devices/push/config', ...options });\n }\n}\n\n// =========================\n// PushDelivery\n// =========================\nclass PushDelivery extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push/deliveries' });\n }\n}\n\nclass PushDeliveryList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushDelivery, endpoint: '/api/account/devices/push/deliveries', ...options });\n }\n}\n\n// =========================\n// Forms\n// =========================\nconst PushConfigForms = {\n create: {\n title: 'Create Push Configuration',\n fields: [\n { name: 'name', label: 'Name', required: true },\n { type: 'collection', name: 'group', label: 'Group (optional)', Collection: GroupList, labelField: 'name', valueField: 'id' },\n { name: 'fcm_service_account', label: 'Service Account', type: \"textarea\", rows: 10},\n ]\n },\n edit: {\n title: 'Edit Push Configuration',\n fields: [\n { name: 'name', label: 'Name', required: true },\n { type: 'collection', name: 'group', label: 'Group (optional)', Collection: GroupList, labelField: 'name', valueField: 'id' },\n { name: 'fcm_service_account', label: 'Service Account', type: \"textarea\", rows: 10},\n { name: 'is_active', label: 'Is Active', type: 'switch', value: true },\n ]\n }\n};\n\nconst PushTemplateForms = {\n edit: {\n title: 'Edit Push Template',\n fields: [\n { name: 'name', label: 'Name', required: true },\n { name: 'category', label: 'Category', required: true },\n { \n type: 'collection', \n name: 'group', \n label: 'Group (optional)', \n Collection: GroupList, \n labelField: 'name', \n valueField: 'id',\n defaultParams: { is_active: true } // Example: filter to active groups only\n },\n { name: 'title_template', label: 'Title Template', required: true },\n { name: 'body_template', label: 'Body Template', type: 'textarea', required: true },\n { name: 'action_url', label: 'Action URL' },\n { name: 'priority', label: 'Priority', type: 'select', options: ['high', 'normal'] },\n { name: 'variables', label: 'Variables', type: 'json', help: 'JSON format' },\n { name: 'is_active', label: 'Is Active', type: 'switch' },\n ]\n }\n};\n\nPushConfigForms.create = PushConfigForms.edit;\nPushTemplateForms.create = PushTemplateForms.edit;\n\n\nexport {\n PushDevice, PushDeviceList,\n PushTemplate, PushTemplateList,\n PushConfig, PushConfigList,\n PushDelivery, PushDeliveryList,\n PushConfigForms, PushTemplateForms\n};\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * GeoLocatedIP\n * ========================= */\n\nconst GeoIPForms = {\n editLocation: {\n title: 'Edit Location',\n size: 'lg',\n fields: [\n { name: 'ip_address', label: 'IP Address', type: 'text', required: true, readonly: true, cols: 6 },\n { name: 'subnet', label: 'Subnet', type: 'text', cols: 6 },\n { name: 'country_name', label: 'Country', type: 'text', cols: 6 },\n { name: 'country_code', label: 'Country Code', type: 'text', cols: 6 },\n { name: 'region', label: 'Region', type: 'text', cols: 6 },\n { name: 'city', label: 'City', type: 'text', cols: 6 },\n { name: 'postal_code', label: 'Postal Code', type: 'text', cols: 6 },\n { name: 'timezone', label: 'Timezone', type: 'text', cols: 6 },\n { name: 'latitude', label: 'Latitude', type: 'number', step: 'any', cols: 6 },\n { name: 'longitude', label: 'Longitude', type: 'number', step: 'any', cols: 6 },\n ]\n },\n editSecurity: {\n title: 'Edit Security',\n size: 'md',\n fields: [\n { \n name: 'threat_level', \n label: 'Threat Level', \n type: 'select', \n cols: 12,\n options: [\n { value: '', label: 'None' },\n { value: 'low', label: 'Low' },\n { value: 'medium', label: 'Medium' },\n { value: 'high', label: 'High' },\n { value: 'critical', label: 'Critical' }\n ]\n },\n { name: 'is_threat', label: 'Threat', type: 'switch', cols: 6 },\n { name: 'is_suspicious', label: 'Suspicious', type: 'switch', cols: 6 },\n { name: 'is_known_attacker', label: 'Known Attacker', type: 'switch', cols: 6 },\n { name: 'is_known_abuser', label: 'Known Abuser', type: 'switch', cols: 6 },\n { name: 'risk_score', label: 'Risk Score', type: 'number', cols: 6 },\n { name: 'is_tor', label: 'TOR Exit Node', type: 'switch', cols: 6 },\n { name: 'is_vpn', label: 'VPN', type: 'switch', cols: 6 },\n { name: 'is_proxy', label: 'Proxy', type: 'switch', cols: 6 },\n { name: 'is_cloud', label: 'Cloud Provider', type: 'switch', cols: 6 },\n { name: 'is_datacenter', label: 'Datacenter', type: 'switch', cols: 6 }\n ]\n },\n editNetwork: {\n title: 'Edit Network',\n size: 'md',\n fields: [\n { name: 'asn', label: 'ASN', type: 'text', cols: 6 },\n { name: 'asn_org', label: 'ASN Organization', type: 'text', cols: 6 },\n { name: 'isp', label: 'ISP', type: 'text', cols: 12 },\n { name: 'connection_type', label: 'Connection Type', type: 'text', cols: 6 },\n { name: 'provider', label: 'Provider', type: 'text', cols: 6 },\n { name: 'is_mobile', label: 'Mobile Connection', type: 'switch', cols: 6 },\n { name: 'mobile_carrier', label: 'Mobile Carrier', type: 'text', cols: 6 },\n { name: 'last_seen', label: 'Last Seen', type: 'datetime', cols: 12 }\n ]\n }\n};\n\nclass GeoLocatedIP extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/system/geoip',\n });\n }\n\n static async lookup(ip) {\n const model = new GeoLocatedIP();\n const resp = await model.rest.GET('/api/system/geoip/lookup', { ip });\n if (resp.success && resp.data && resp.data.data) {\n return new GeoLocatedIP(resp.data.data);\n }\n return null;\n }\n}\n\n// Attach forms to model (use Location as default EDIT_FORM for TableView)\nGeoLocatedIP.EDIT_FORM = GeoIPForms.editLocation;\nGeoLocatedIP.EDIT_LOCATION_FORM = GeoIPForms.editLocation;\nGeoLocatedIP.EDIT_SECURITY_FORM = GeoIPForms.editSecurity;\nGeoLocatedIP.EDIT_NETWORK_FORM = GeoIPForms.editNetwork;\n\nclass GeoLocatedIPList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: GeoLocatedIP,\n endpoint: '/api/system/geoip',\n ...options,\n });\n }\n}\n\nexport { GeoLocatedIP, GeoLocatedIPList };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport { UserList } from './User.js';\nimport { IncidentList } from './Incident.js';\n\n/* =========================\n * Ticket\n * ========================= */\nclass Ticket extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/ticket',\n });\n }\n}\n\nclass TicketList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Ticket,\n endpoint: '/api/incident/ticket',\n ...options,\n });\n }\n}\n\n\nconst TicketCategories = {\n 'ticket': 'Ticket',\n 'bug': 'Bug',\n 'feature': 'Feature Request',\n 'incident': 'Incident',\n 'security': 'Security Incident',\n 'fulfillment': 'Fulfillment',\n 'new_user': 'New User',\n 'new_group': 'New Group',\n 'qa': 'Quality Assurance'\n};\n\n// Convert TicketCategories to select options\nconst TicketCategoriesOptions = Object.entries(TicketCategories).map(([key, label]) => ({\n value: key,\n label: label\n}));\n\n\nconst TicketForms = {\n create: {\n title: 'Create Ticket',\n fields: [\n {\n name: 'title', type: 'text',\n label: 'Title', required: true, cols: 12\n },\n { name: 'description', type: 'textarea', label: 'Description', required: false, cols: 12 },\n {\n name: \"category\", type: \"select\", label: \"Category\",\n options: TicketCategoriesOptions, cols: 12, value: 'ticket' },\n {\n name: 'priority', type: 'number',\n label: 'Priority', value: 5, cols: 6\n },\n {\n name: 'status', type: 'select', label: \"Status\",\n options: [\"new\", \"open\", \"paused\", \"resolved\", \"qa\", \"ignored\"],\n cols: 6, value: 'new'\n },\n {\n type: 'collection', name: 'assignee',\n label: 'Assignee', Collection: UserList,\n labelField: 'display_name',\n valueField: 'id',\n cols: 12\n },\n {\n type: 'collection', name: 'incident',\n label: 'Incident', Collection: IncidentList,\n labelField: 'title',\n valueField: 'id',\n cols: 12\n },\n ]\n },\n edit: {\n title: 'Edit Ticket',\n fields: [\n { name: 'title', type: 'text', label: 'Title', required: true, cols: 12 },\n { name: 'description', type: 'textarea', label: 'Description', required: false, cols: 12 },\n { name: \"category\", type: \"select\", label: \"Category\", options: TicketCategoriesOptions, cols: 12 },\n { name: 'priority', type: 'number', label: 'Priority', cols: 6 },\n { name: 'status', type: 'select', label: 'Status', options: [\"new\", \"open\", \"paused\", \"resolved\", \"qa\", \"ignored\"], cols: 6 },\n { type: 'collection', name: 'assignee', label: 'Assignee', Collection: UserList, labelField: 'display_name', valueField: 'id', cols: 12 },\n { type: 'collection', name: 'incident', label: 'Incident', Collection: IncidentList, labelField: 'title', valueField: 'id', cols: 12 },\n ]\n }\n};\n\n/* =========================\n * TicketNote\n * ========================= */\nclass TicketNote extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/ticket/note',\n });\n }\n}\n\nclass TicketNoteList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: TicketNote,\n endpoint: '/api/incident/ticket/note',\n ...options,\n });\n }\n}\n\nexport { Ticket, TicketList, TicketNote, TicketNoteList, TicketForms, TicketCategories };\n","/**\n * TableRow - Individual row view for TableView\n *\n * Extends ListViewItem to render table rows with proper cell formatting\n * and support for all table features like selection, actions, and context menus.\n *\n * @example\n * const row = new TableRow({\n * model: userModel,\n * columns: tableColumns,\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport ListViewItem from '../list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TableRow extends ListViewItem {\n constructor(options = {}) {\n super({\n tagName: 'tr',\n className: 'table-row',\n enableTooltips: true,\n ...options\n });\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.tableView = options.tableView || options.listView || null;\n\n // Inline editing state\n this.editingCells = new Set(); // Track which cells are being edited\n\n // Override template to generate table cells\n this.template = this.buildRowTemplate();\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Build the row template with table cells\n */\n buildRowTemplate() {\n let template = '';\n\n // Selection checkbox cell\n if (this.tableView && this.tableView.isSelectable()) {\n template += `\n <td style=\"padding: 0;\">\n <div class=\"mojo-select-cell {{#selected}}selected{{/selected}}\"\n data-action=\"select\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </td>\n `;\n }\n\n // Data cells for each column\n this.columns.forEach(column => {\n const cellClass = column.class || column.className || '';\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n const editableClass = column.editable ? 'editable-cell' : '';\n const combinedClasses = [cellClass, responsiveClasses, editableClass].filter(c => c).join(' ');\n const cellContent = this.buildCellTemplate(column);\n\n // Determine cell action\n let cellAction = column.action;\n if (!cellAction && column.editable) {\n cellAction = 'edit-cell';\n } else if (!cellAction && this.tableView.rowAction) {\n cellAction = this.tableView.rowAction;\n }\n\n if (cellAction) {\n template += `<td class=\"${combinedClasses}\" data-action=\"${cellAction}\" data-column=\"${column.key}\">${cellContent}</td>`;\n } else {\n template += `<td class=\"${combinedClasses}\" data-column=\"${column.key}\">${cellContent}</td>`;\n }\n });\n\n // Actions cell\n if (this.actions) {\n template += this.buildActionsTemplate();\n } else if (this.contextMenu) {\n template += this.buildContextMenuTemplate();\n }\n\n return template;\n }\n\n /**\n * Build template for a single cell\n */\n /**\n * Build template for a single cell\n */\n buildCellTemplate(column) {\n // Build path for Mustache to access the value\n const path = `model.${column.key}`;\n // Support both 'formatter' and 'format' for consistency with DataView\n const formatter = column.formatter || column.format;\n if (formatter) {\n // For string formatters that are pipe expressions\n if (typeof formatter === 'string') {\n return `{{{${path}|${formatter}}}}`;\n } else if (typeof formatter === 'function') {\n return `<span data-formatter=\"${column.key}\">{{${path}}}</span>`;\n }\n }\n\n if (column.template) {\n return column.template;\n }\n\n // For editable cells, wrap content in a span for easy replacement\n if (column.editable) {\n return `<span class=\"cell-content\" data-field=\"${column.key}\">{{{${path}}}}</span>`;\n }\n\n return `{{{${path}}}}`;\n }\n\n /**\n * Build actions cell template\n */\n buildActionsTemplate() {\n if (!this.actions || this.actions.length === 0) return '';\n\n const buttons = this.actions.map(action => {\n if (typeof action === 'string') {\n switch (action) {\n case 'view':\n return `\n <button class=\"btn btn-sm btn-outline-primary\"\n data-action=\"view\"\n title=\"View\">\n <i class=\"bi bi-eye\"></i>\n </button>\n `;\n\n case 'edit':\n return `\n <button class=\"btn btn-sm btn-outline-secondary\"\n data-action=\"edit\"\n title=\"Edit\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n `;\n\n case 'delete':\n return `\n <button class=\"btn btn-sm btn-outline-danger\"\n data-action=\"delete\"\n title=\"Delete\">\n <i class=\"bi bi-trash\"></i>\n </button>\n `;\n\n default:\n return '';\n }\n } else if (typeof action === 'object') {\n return `\n <button class=\"btn btn-sm ${action.class || 'btn-outline-primary'}\"\n data-id=\"${this.model.id}\"\n data-action=\"${action.action}\"\n title=\"${action.label || ''}\">\n ${action.icon ? `<i class=\"${action.icon}\"></i>` : ''}\n ${action.label && !action.icon ? action.label : ''}\n </button>\n `;\n }\n return '';\n }).join('');\n\n return `<td><div class=\"btn-group btn-group-sm\">${buttons}</div></td>`;\n }\n\n /**\n * Build context menu cell template\n */\n buildContextMenuTemplate() {\n if (!this.contextMenu || this.contextMenu.length === 0) return '';\n\n return `\n <td class=\"text-end\" style=\"width: 1px;\">\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-link border-0\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n style=\"color: #6c757d;\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end shadow-sm\">\n ${this.buildContextMenuItems()}\n </ul>\n </div>\n </td>\n `;\n }\n\n /**\n * Build context menu items\n */\n buildContextMenuItems() {\n return this.contextMenu.map(menuItem => {\n if (menuItem.separator||menuItem.divider) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n let itemClass = 'dropdown-item';\n if (menuItem.action === 'delete' || menuItem.danger) {\n itemClass += ' text-danger';\n }\n if (menuItem.disabled) {\n itemClass += ' disabled';\n }\n\n return `\n <li>\n <a class=\"${itemClass}\" href=\"#\"\n data-id=\"{{model.id}}\"\n data-action=\"${menuItem.action}\"\n ${menuItem.disabled ? 'aria-disabled=\"true\" tabindex=\"-1\"' : ''}>\n ${menuItem.icon ? `<i class=\"${menuItem.icon} me-2\"></i>` : ''}\n ${menuItem.label}\n </a>\n </li>\n `;\n }).join('');\n }\n\n /**\n * Override onAfterRender to apply function formatters and templates\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Apply function formatters\n this.columns.forEach(column => {\n if (column.formatter && typeof column.formatter === 'function') {\n const cell = this.element.querySelector(`[data-formatter=\"${column.key}\"]`);\n if (cell) {\n const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n const context = {\n value,\n row: this.model, // deprecate this\n model: this.model,\n column,\n table: this.tableView,\n index: this.index\n };\n try {\n cell.innerHTML = column.formatter(value, context);\n } catch (error) {\n console.error(`Error formatting cell for column ${column.key}:`, error);\n }\n }\n }\n\n // Apply function templates\n // if (column.template && typeof column.template === 'function') {\n // const cell = this.element.querySelector(`[data-template=\"${column.key}\"]`);\n // if (cell) {\n // const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n // cell.innerHTML = column.template(value, this.model);\n // }\n // }\n });\n\n // Update selection state\n if (this.selected) {\n this.element.classList.add('selected');\n }\n\n // Set data-id attribute for easy identification\n const id = this.model.get ? this.model.get('id') : this.model.id;\n if (id) {\n this.element.setAttribute('data-id', id);\n }\n }\n\n /**\n * Handle edit cell action\n */\n async onActionEditCell(event, element) {\n event.stopPropagation();\n\n const columnKey = element.getAttribute('data-column');\n const column = this.columns.find(col => col.key === columnKey);\n\n if (!column || !column.editable) return;\n\n // Don't enter edit mode if already editing this cell\n if (this.editingCells.has(columnKey)) return;\n\n await this.enterEditMode(columnKey, column, element);\n }\n\n /**\n * Handle row click action\n */\n async onActionRowClick(event, element) {\n // Don't trigger row click if clicking on action buttons or editing\n if (event.target.closest('.btn-group') || event.target.closest('.dropdown') || event.target.closest('.cell-editor')) {\n return;\n }\n\n // Emit row click event\n this.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n\n // Notify parent TableView\n if (this.tableView) {\n this.tableView.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n }\n }\n\n /**\n * Handle view action\n */\n async onActionView(event, element) {\n event.stopPropagation();\n\n this.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Handle edit action\n */\n async onActionEdit(event, element) {\n event.stopPropagation();\n\n this.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n }\n return true;\n }\n\n /**\n * Handle delete action\n */\n async onActionDelete(event, element) {\n event.stopPropagation();\n\n this.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Enter edit mode for a cell\n */\n async enterEditMode(columnKey, column, cellElement) {\n const contentSpan = cellElement.querySelector('.cell-content');\n if (!contentSpan) return;\n\n this.editingCells.add(columnKey);\n const currentValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Create editor based on column configuration\n const editor = this.createCellEditor(column, currentValue);\n\n // Replace content with editor\n const originalContent = contentSpan.innerHTML;\n contentSpan.style.display = 'none';\n\n const editorContainer = document.createElement('div');\n editorContainer.className = 'cell-editor';\n editorContainer.innerHTML = editor;\n cellElement.appendChild(editorContainer);\n\n // Focus the input\n const input = editorContainer.querySelector('input, select, .form-check-input');\n if (input) {\n input.focus();\n if (input.type === 'text' || input.type === 'textarea') {\n input.select();\n }\n }\n\n // Store original content for cancel\n editorContainer.dataset.originalContent = originalContent;\n editorContainer.dataset.columnKey = columnKey;\n\n // Set up event listeners\n this.setupEditorEvents(editorContainer, columnKey, column);\n\n this.emit('cell:edit', {\n row: this,\n model: this.model,\n column: columnKey,\n originalValue: currentValue\n });\n }\n\n /**\n * Create cell editor HTML based on column configuration\n */\n createCellEditor(column, currentValue) {\n const options = column.editableOptions || {};\n\n switch (options.type) {\n case 'select':\n return this.createSelectEditor(options, currentValue);\n case 'switch':\n case 'checkbox':\n return this.createSwitchEditor(options, currentValue);\n case 'textarea':\n return this.createTextareaEditor(options, currentValue);\n default:\n return this.createTextEditor(options, currentValue);\n }\n }\n\n /**\n * Create text input editor\n */\n createTextEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const inputType = options.inputType || 'text';\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <input type=\"${inputType}\"\n class=\"form-control form-control-sm cell-input\"\n value=\"${this.escapeHtml(currentValue || '')}\"\n placeholder=\"${placeholder}\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create textarea editor\n */\n createTextareaEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const rows = options.rows || 2;\n\n return `\n <div class=\"d-flex gap-1\">\n <textarea class=\"form-control form-control-sm cell-input\"\n rows=\"${rows}\"\n placeholder=\"${placeholder}\">${this.escapeHtml(currentValue || '')}</textarea>\n <div class=\"d-flex flex-column gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Create select dropdown editor\n */\n createSelectEditor(options, currentValue) {\n const optionsArray = options.options || [];\n let optionsHtml = '';\n\n optionsArray.forEach(option => {\n if (typeof option === 'string') {\n const selected = option === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option}\" ${selected}>${option}</option>`;\n } else if (typeof option === 'object' && option.value !== undefined) {\n const selected = option.value === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option.value}\" ${selected}>${option.label || option.value}</option>`;\n }\n });\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm cell-input\">\n ${optionsHtml}\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create switch/checkbox editor\n */\n createSwitchEditor(options, currentValue) {\n const checked = currentValue ? 'checked' : '';\n const switchType = options.type === 'switch' ? 'form-switch' : '';\n\n return `\n <div class=\"d-flex gap-2 align-items-center\">\n <div class=\"form-check ${switchType}\">\n <input class=\"form-check-input cell-input\" type=\"checkbox\" ${checked}>\n </div>\n <div class=\"d-flex gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Setup event listeners for cell editor\n */\n setupEditorEvents(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n const saveBtn = editorContainer.querySelector('.cell-save');\n const cancelBtn = editorContainer.querySelector('.cell-cancel');\n\n // Save on Enter (for text inputs)\n if (input && (input.type === 'text' || input.type === 'email' || input.type === 'number')) {\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.saveCellEdit(editorContainer, columnKey, column);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.cancelCellEdit(editorContainer, columnKey);\n }\n });\n }\n\n // Save on change for selects and checkboxes (if auto-save enabled)\n if (input && (input.type === 'checkbox' || input.tagName === 'SELECT') && column.autoSave !== false) {\n input.addEventListener('change', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n }\n\n // Button events\n saveBtn?.addEventListener('click', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n\n cancelBtn?.addEventListener('click', () => {\n this.cancelCellEdit(editorContainer, columnKey);\n });\n }\n\n /**\n * Save cell edit\n */\n async saveCellEdit(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n if (!input) return;\n\n let newValue;\n\n // Extract value based on input type\n if (input.type === 'checkbox') {\n newValue = input.checked;\n } else if (input.tagName === 'SELECT') {\n newValue = input.value;\n } else {\n newValue = input.value;\n }\n\n const oldValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Save to model and backend\n try {\n if (this.model.save) {\n await this.model.save({ [columnKey]: newValue });\n } else {\n // Fallback for models without save method\n this.model[columnKey] = newValue;\n }\n\n // Exit edit mode\n this.exitEditMode(editorContainer, columnKey, newValue);\n\n // Emit save event\n this.emit('cell:save', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue\n });\n\n } catch (error) {\n // Show error and keep in edit mode\n console.error('Failed to save cell edit:', error);\n this.emit('cell:save:error', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue,\n error: error\n });\n\n // Could show an error message in the UI\n editorContainer.classList.add('saving-error');\n setTimeout(() => editorContainer.classList.remove('saving-error'), 3000);\n }\n }\n\n /**\n * Cancel cell edit\n */\n cancelCellEdit(editorContainer, columnKey) {\n const originalContent = editorContainer.dataset.originalContent;\n this.exitEditMode(editorContainer, columnKey, null, originalContent);\n\n this.emit('cell:cancel', {\n row: this,\n model: this.model,\n column: columnKey\n });\n }\n\n /**\n * Exit edit mode and restore content\n */\n exitEditMode(editorContainer, columnKey, newValue = null, originalContent = null) {\n const cellElement = editorContainer.closest('td');\n const contentSpan = cellElement.querySelector('.cell-content');\n\n if (contentSpan) {\n if (newValue !== null) {\n // Update display with new value (with proper formatting if needed)\n const column = this.columns.find(col => col.key === columnKey);\n let displayValue = newValue;\n\n if (column && column.formatter && typeof column.formatter === 'string') {\n displayValue = dataFormatter.pipe(newValue, column.formatter);\n }\n\n contentSpan.innerHTML = this.escapeHtml(displayValue);\n } else if (originalContent) {\n // Restore original content on cancel\n contentSpan.innerHTML = originalContent;\n }\n\n contentSpan.style.display = '';\n }\n\n // Remove editor\n editorContainer.remove();\n this.editingCells.delete(columnKey);\n }\n\n /**\n * Escape HTML for safe display\n */\n escapeHtml(text) {\n if (text === null || text === undefined) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Override select to handle table-specific selection UI\n */\n select() {\n super.select();\n this.addClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.add('selected');\n }\n }\n\n /**\n * Override deselect to handle table-specific selection UI\n */\n deselect() {\n super.deselect();\n this.removeClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.remove('selected');\n }\n }\n}\n\nexport default TableRow;\n","/**\n * DjangoLookups - Utility for Django-style filter lookup parsing and formatting\n * \n * Provides utilities to parse filter keys like \"status__in\" or \"created__gte\"\n * and format them into human-readable display text for filter pills.\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * formatFilterDisplay('status__in', 'new,open', 'Status') // \"Status in 'new', 'open'\"\n */\n\n/**\n * Supported Django-style lookups with display configurations\n * Only includes commonly used lookups (KISS principle)\n */\nexport const LOOKUPS = {\n // Comparison\n 'exact': { \n display: 'is',\n description: 'Exact match'\n },\n 'in': { \n display: 'in',\n description: 'Match any of the values (comma-separated)'\n },\n 'not': { \n display: 'is not',\n description: 'Does not match'\n },\n 'not_in': { \n display: 'not in',\n description: 'Does not match any of the values'\n },\n 'gt': { \n display: '>',\n description: 'Greater than'\n },\n 'gte': { \n display: '>=',\n description: 'Greater than or equal to'\n },\n 'lt': { \n display: '<',\n description: 'Less than'\n },\n 'lte': { \n display: '<=',\n description: 'Less than or equal to'\n },\n \n // String operations\n 'contains': { \n display: 'contains',\n description: 'Contains substring (case-sensitive)'\n },\n 'icontains': { \n display: 'contains',\n description: 'Contains substring (case-insensitive)'\n },\n 'startswith': { \n display: 'starts with',\n description: 'Starts with substring (case-sensitive)'\n },\n 'istartswith': { \n display: 'starts with',\n description: 'Starts with substring (case-insensitive)'\n },\n 'endswith': { \n display: 'ends with',\n description: 'Ends with substring (case-sensitive)'\n },\n 'iendswith': { \n display: 'ends with',\n description: 'Ends with substring (case-insensitive)'\n },\n \n // Null checks\n 'isnull': { \n display: (val) => val === 'true' || val === true ? 'is null' : 'is not null',\n description: 'Check if value is null or not'\n },\n \n // Range operations\n 'range': { \n display: 'between',\n description: 'Between two values (comma-separated)'\n }\n};\n\n/**\n * Parse a filter key into field name and lookup operator\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\", \"created__gte\")\n * @returns {Object} Object with field and lookup properties\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * parseFilterKey('status') // { field: 'status', lookup: null }\n * parseFilterKey('user__profile__name__icontains') // { field: 'user__profile__name', lookup: 'icontains' }\n */\nexport function parseFilterKey(paramKey) {\n if (!paramKey || typeof paramKey !== 'string') {\n return { field: paramKey, lookup: null };\n }\n\n const parts = paramKey.split('__');\n \n // Single part, no lookup\n if (parts.length === 1) {\n return { field: paramKey, lookup: null };\n }\n \n // Check if last part is a valid lookup\n const possibleLookup = parts[parts.length - 1];\n if (LOOKUPS[possibleLookup]) {\n return { \n field: parts.slice(0, -1).join('__'), \n lookup: possibleLookup \n };\n }\n \n // No valid lookup found, treat entire string as field name\n return { field: paramKey, lookup: null };\n}\n\n/**\n * Format a filter key and value into human-readable display text\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\")\n * @param {string|Array} value - Filter value(s)\n * @param {string} label - Human-readable field label\n * @returns {string} Formatted display text\n * \n * @example\n * formatFilterDisplay('status__in', 'new,open', 'Status') \n * // \"Status in 'new', 'open'\"\n * \n * formatFilterDisplay('created__gte', '2025-01-01', 'Created') \n * // \"Created >= '2025-01-01'\"\n * \n * formatFilterDisplay('name__icontains', 'john', 'Name') \n * // \"Name contains 'john'\"\n */\nexport function formatFilterDisplay(paramKey, value, label) {\n if (!paramKey || value === null || value === undefined) {\n return '';\n }\n\n const { field, lookup } = parseFilterKey(paramKey);\n const lookupDef = LOOKUPS[lookup];\n \n // Convert array to comma-separated string if needed\n const valueStr = Array.isArray(value) ? value.join(',') : String(value);\n \n // No lookup or exact lookup - simple \"is\" format\n if (!lookup || lookup === 'exact') {\n return `${label} is '${valueStr}'`;\n }\n \n // Multi-value lookups (in, not_in)\n if (lookup === 'in' || lookup === 'not_in') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 0) {\n return `${label} ${lookupDef.display}`;\n }\n const formattedValues = values.map(v => `'${v}'`).join(', ');\n return `${label} ${lookupDef.display} ${formattedValues}`;\n }\n \n // Range lookup - special formatting\n if (lookup === 'range') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 2) {\n return `${label} between '${values[0]}' and '${values[1]}'`;\n }\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Null check - dynamic display based on value\n if (lookup === 'isnull') {\n const displayText = typeof lookupDef.display === 'function' \n ? lookupDef.display(valueStr) \n : lookupDef.display;\n return `${label} ${displayText}`;\n }\n \n // Standard lookup with operator\n if (lookupDef) {\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Fallback for unknown lookups\n return `${label} is '${valueStr}'`;\n}\n\n/**\n * Get a user-friendly description of a lookup operator\n * \n * @param {string} lookup - Lookup operator (e.g., \"in\", \"gte\", \"icontains\")\n * @returns {string} Human-readable description\n * \n * @example\n * getLookupDescription('in') // \"Match any of the values (comma-separated)\"\n * getLookupDescription('gte') // \"Greater than or equal to\"\n */\nexport function getLookupDescription(lookup) {\n const lookupDef = LOOKUPS[lookup];\n return lookupDef ? lookupDef.description : 'Exact match';\n}\n\n/**\n * Check if a string is a valid lookup operator\n * \n * @param {string} lookup - Potential lookup operator\n * @returns {boolean} True if valid lookup\n * \n * @example\n * isValidLookup('in') // true\n * isValidLookup('foo') // false\n */\nexport function isValidLookup(lookup) {\n return lookup && LOOKUPS.hasOwnProperty(lookup);\n}\n\n/**\n * Get all available lookup operators\n * \n * @returns {Array<string>} Array of lookup operator names\n * \n * @example\n * getAvailableLookups() // ['exact', 'in', 'not', 'not_in', 'gt', ...]\n */\nexport function getAvailableLookups() {\n return Object.keys(LOOKUPS);\n}\n\n/**\n * Build a filter key from field name and lookup operator\n * \n * @param {string} field - Field name\n * @param {string} lookup - Lookup operator (optional)\n * @returns {string} Combined filter key\n * \n * @example\n * buildFilterKey('status', 'in') // \"status__in\"\n * buildFilterKey('status') // \"status\"\n */\nexport function buildFilterKey(field, lookup = null) {\n if (!field) return '';\n if (!lookup) return field;\n return `${field}__${lookup}`;\n}\n\nexport default {\n LOOKUPS,\n parseFilterKey,\n formatFilterDisplay,\n getLookupDescription,\n isValidLookup,\n getAvailableLookups,\n buildFilterKey\n};\n","/**\n * TableView - Advanced data table component extending ListView\n *\n * Leverages ListView's view management system for efficient row rendering.\n * Each row is a separate TableRow view that only re-renders when its model changes.\n *\n * @example\n * const table = new TableView({\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email', visibility: 'md' }, // Hidden on xs/sm, visible md+\n * { key: 'phone', label: 'Phone', visibility: 'lg' }, // Visible only on lg+\n * { key: 'created', label: 'Created', formatter: 'date', visibility: 'xl' } // Visible only on xl+\n * ],\n * actions: ['view', 'edit', 'delete'],\n * selectionMode: 'multiple'\n * });\n */\n\nimport ListView from '../list/ListView.js';\nimport TableRow from './TableRow.js';\nimport Mustache from '@core/utils/mustache.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport FormView from '@core/forms/FormView.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\nimport { parseFilterKey, formatFilterDisplay } from '@core/utils/DjangoLookups.js';\n\nclass TableView extends ListView {\n constructor(options = {}) {\n // Set up table-specific defaults before calling super\n const tableOptions = {\n className: 'table-view-component',\n itemClass: options.itemClass || TableRow,\n selectionMode: options.selectable ? 'multiple' : 'none',\n emptyMessage: options.emptyMessage || 'No data available',\n addButtonIcon: options.addButtonIcon || 'bi bi-plus-circle',\n ...options\n };\n\n super(tableOptions);\n\n // Fullscreen state\n this.isFullscreen = false;\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.searchable = options.searchable !== false;\n this.sortable = options.sortable !== false;\n this.filterable = options.filterable !== false;\n this.paginated = options.paginated !== false;\n this.clickAction = options.clickAction || \"view\";\n\n // Model operation configurations\n this.itemView = options.itemView;\n this.addForm = options.addForm;\n this.editForm = options.editForm;\n this.deleteTemplate = options.deleteTemplate;\n this.formDialogConfig = options.formDialogConfig || {};\n this.viewDialogOptions = options.viewDialogOptions || {};\n\n // Export configuration\n this.exportOptions = options.exportOptions || null;\n if (this.options.showExport && !this.exportOptions) {\n this.exportOptions = [\n { format: 'csv', label: 'Export as CSV', icon: 'bi bi-file-earmark-spreadsheet' },\n { format: 'json', label: 'Export as JSON', icon: 'bi bi-file-earmark-code' }\n ];\n }\n this.exportSource = options.exportSource || 'remote';\n\n // Filter configuration\n this.filters = {};\n this.additionalFilters = options.filters || [];\n this.hideActivePills = options.hideActivePills || false;\n this.hideActivePillNames = options.hideActivePillNames || [];\n this.rowAction = options.rowAction || \"row-click\";\n this.batchBarLocation = options.batchBarLocation || \"bottom\"; // \"top\" or \"bottom\"\n\n this.options.addButtonLabel = options.addButtonLabel || 'Add';\n\n // Custom toolbar buttons\n this.toolbarButtons = options.toolbarButtons || [];\n\n // Table display options\n this.tableOptions = {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n size: null, // null, 'sm', 'lg'\n ...options.tableOptions\n };\n\n // Search configuration\n this.searchPlacement = options.searchPlacement || 'toolbar'; // 'toolbar' or 'dropdown'\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n\n // Initialize column configuration BEFORE building template\n this.initializeColumns();\n\n // Extract filters from columns BEFORE building template\n this.extractColumnFilters();\n\n // Detect columns that need footer totals\n this.footerTotalColumns = this.columns.filter(col => col.footer_total === true);\n this.hasFooterTotals = this.footerTotalColumns.length > 0;\n\n // Build template with Mustache variables\n this.template = this.buildTableTemplate();\n\n // Listen for collection changes to update totals\n this.setupCollectionListeners();\n }\n\n /**\n * Setup collection event listeners for totals updates\n */\n setupCollectionListeners() {\n if (this.hasFooterTotals && this.collection) {\n // Re-render totals when collection data changes\n this.collection.on('reset add remove change', () => {\n this.updateFooterTotals();\n });\n }\n }\n\n /**\n * Initialize column configuration\n */\n initializeColumns() {\n this.columns.forEach(column => {\n // Ensure each column has a key\n if (!column.key && column.name) {\n column.key = column.name;\n }\n\n // Set default label if not provided\n if (!column.label && !column.title) {\n column.label = column.key.charAt(0).toUpperCase() + column.key.slice(1);\n }\n });\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Extract column key and formatter from combined key (e.g., \"sales_amount|currency\")\n */\n parseColumnKey(key) {\n const parts = key.split('|');\n return {\n fieldKey: parts[0],\n formatter: parts[1] || null\n };\n }\n\n /**\n * Update footer totals in the DOM without full re-render\n */\n updateFooterTotals() {\n if (!this.hasFooterTotals || !this.element) return;\n\n const totals = this.calculateFooterTotals();\n console.log('Updating footer totals in DOM:', totals);\n\n // Update each total cell in the footer\n let totalColumnIndex = 0;\n this.columns.forEach((column) => {\n if (column.footer_total) {\n const safeKey = `col_${totalColumnIndex}`;\n const cell = this.element.querySelector(`[data-total-column=\"${safeKey}\"]`);\n\n if (cell && totals[safeKey]) {\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n let displayValue;\n\n if (formatter && typeof formatter === 'string') {\n // Use DataFormatter if available\n displayValue = this.formatValue(totals[safeKey].value, formatter);\n } else {\n displayValue = totals[safeKey].value;\n }\n\n cell.textContent = displayValue;\n }\n totalColumnIndex++;\n }\n });\n }\n\n /**\n * Format a value using DataFormatter\n */\n formatValue(value, formatter) {\n try {\n return dataFormatter.pipe(value, formatter);\n } catch (e) {\n console.warn('Error formatting value:', e);\n return value;\n }\n }\n\n /**\n * Calculate totals for footer columns\n */\n calculateFooterTotals() {\n if (!this.hasFooterTotals || !this.collection || this.collection.length === 0) {\n return {};\n }\n\n const totals = {};\n\n this.footerTotalColumns.forEach((column, totalColumnIndex) => {\n const { fieldKey, formatter } = this.parseColumnKey(column.key);\n let sum = 0;\n\n // Sum values from all items in collection\n this.collection.forEach(model => {\n const value = model.get ? model.get(fieldKey) : model[fieldKey];\n const numValue = parseFloat(value) || 0;\n sum += numValue;\n });\n\n // Debug logging\n console.log(`Footer total for ${column.key}: ${sum} (from ${this.collection.length} items)`);\n\n // Use safe key for Mustache (avoid special characters)\n const safeKey = `col_${totalColumnIndex}`;\n\n // Store total with formatter info\n totals[safeKey] = {\n value: sum,\n formatter: formatter || column.formatter,\n fieldKey: fieldKey,\n originalKey: column.key\n };\n });\n\n return totals;\n }\n\n /**\n * Extract filters from column configuration\n */\n extractColumnFilters() {\n this.filters = {};\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n this.filters[fieldKey] = column.filter;\n }\n });\n }\n\n isSelectable() {\n return this.batchActions && this.batchActions.length > 0 && this.selectionMode == 'multiple';\n }\n\n /**\n * Build the complete table template\n */\n buildTableTemplate() {\n const batchPanelTop = this.batchBarLocation === 'top' ? this.buildBatchActionsPanel() : '';\n const batchPanelBottom = this.batchBarLocation === 'bottom' ? this.buildBatchActionsPanel() : '';\n\n return `\n <div class=\"mojo-table-wrapper\">\n ${this.buildToolbarTemplate()}\n ${batchPanelTop}\n <div class=\"table-container\"${(() => { const __fs = (this.tableOptions && this.tableOptions.fontSize != null) ? this.tableOptions.fontSize : (this.options && this.options.fontSize); const __val = __fs === 'sm' ? '0.9rem' : (__fs === 'xs' ? '0.8rem' : (__fs ? String(__fs) : null)); return __val ? ` style=\"font-size: ${__val};\"` : ''; })()}>\n {{#loading}}\n <div class=\"mojo-table-loading d-flex justify-content-center align-items-center py-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"table-empty text-center py-5\">\n <i class=\"bi bi-inbox fa-2x mb-2 text-muted\"></i>\n <p class=\"text-muted\">{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <table class=\"${this.buildTableClasses()}\">\n ${this.buildTableHeaderTemplate()}\n <tbody data-container=\"items\"></tbody>\n ${this.hasFooterTotals ? this.buildTableFooterTemplate() : ''}\n </table>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${batchPanelBottom}\n ${this.buildPaginationTemplate()}\n </div>\n `;\n }\n\n /**\n * Build table CSS classes\n */\n buildTableClasses() {\n let classes = ['table'];\n\n if (this.tableOptions.striped) classes.push('table-striped');\n if (this.tableOptions.bordered) classes.push('table-bordered');\n if (this.tableOptions.hover) classes.push('table-hover');\n if (this.tableOptions.responsive) classes.push('table-responsive');\n if (this.tableOptions.background) classes.push(`table-${this.tableOptions.background}`);\n if (this.tableOptions.size === 'sm') classes.push('table-sm');\n if (this.tableOptions.size === 'lg') classes.push('table-lg');\n\n return classes.join(' ');\n }\n\n /**\n * Build toolbar template\n */\n buildToolbarTemplate() {\n if (!this.searchable && !this.filterable) {\n return '';\n }\n\n return `\n <div class=\"table-action-buttons mb-3\">\n <div class=\"d-flex align-items-center gap-2\">\n ${this.buildActionButtonsTemplate()}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n\n </div>\n <div data-container=\"filter-pills\"></div>\n </div>\n `;\n }\n\n /**\n * Build action buttons template\n */\n buildActionButtonsTemplate() {\n let buttons = [];\n\n // Refresh button\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-refresh\"\n data-action=\"refresh\"\n title=\"Refresh\">\n <i class=\"bi bi-arrow-clockwise\"></i>\n </button>\n `);\n\n // Fullscreen button (only if browser supports it)\n if (this.isFullscreenSupported()) {\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-fullscreen\"\n data-action=\"toggle-fullscreen\"\n title=\"Toggle Fullscreen\">\n <i class=\"bi bi-fullscreen\"></i>\n </button>\n `);\n }\n\n // Custom action buttons from options\n if (this.options.showAdd) {\n buttons.push(`\n <button class=\"btn btn-sm btn-success btn-add\"\n data-action=\"add\"\n title=\"${this.options.addButtonLabel}\">\n <i class=\"${this.options.addButtonIcon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${this.options.addButtonLabel}</span>\n </button>\n `);\n }\n\n if (this.options.showExport) {\n if (this.exportOptions && this.exportOptions.length > 1) {\n // Dropdown for multiple export options\n const dropdownItems = this.exportOptions.map(opt => `\n <li>\n <a class=\"dropdown-item\" href=\"#\" data-action=\"export\" data-format=\"${opt.format}\">\n <i class=\"${opt.icon || 'bi bi-file-earmark-arrow-down'} me-2\"></i>${opt.label}\n </a>\n </li>\n `).join('');\n\n buttons.push(`\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\" title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n <ul class=\"dropdown-menu\">\n ${dropdownItems}\n </ul>\n </div>\n `);\n } else {\n // Single export button\n const format = this.exportOptions && this.exportOptions.length === 1 ? this.exportOptions[0].format : 'json';\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-export\"\n data-action=\"export\"\n data-format=\"${format}\"\n title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n `);\n }\n }\n\n // if (buttons.length > 0) {\n // buttons.push(`<div class=\"vr mx-2\"></div>`);\n // }\n\n // Render custom toolbar buttons\n if (this.toolbarButtons && this.toolbarButtons.length > 0) {\n this.toolbarButtons.forEach((button, index) => {\n const {\n label = 'Button',\n icon = '',\n action = '',\n handler = null,\n variant = 'outline-secondary',\n title = label,\n className = '',\n permissions = null\n } = button;\n\n // Check permissions if specified\n if (permissions && !this.checkPermissions(permissions)) {\n return;\n }\n\n const iconHtml = icon ? `<i class=\"${icon} me-1\"></i>` : '';\n const labelHtml = `<span class=\"d-none d-lg-inline\">${label}</span>`;\n\n // Use handler if provided, otherwise use action for data-action attribute\n let dataAttrs = '';\n if (handler) {\n dataAttrs = `data-action=\"custom-toolbar-button\" data-button-index=\"${index}\"`;\n } else if (action) {\n dataAttrs = `data-action=\"${action}\"`;\n }\n\n const btnClass = `btn btn-sm btn-${variant} ${className}`.trim();\n\n buttons.push(`\n <button class=\"${btnClass}\"\n ${dataAttrs}\n title=\"${title}\">\n ${iconHtml}${labelHtml}\n </button>\n `);\n });\n }\n\n return buttons.join('');\n }\n\n /**\n * Build search template\n */\n buildSearchTemplate() {\n return `\n <div class=\"flex-grow-1\" style=\"max-width: 400px;\">\n <div class=\"input-group input-group-sm\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input type=\"search\"\n class=\"form-control\"\n placeholder=\"{{searchPlaceholder}}\"\n data-filter=\"search\"\n data-change-action=\"apply-search\"\n value=\"{{collection.params.search}}\"\n aria-label=\"Search\">\n {{#searchValue}}\n <button class=\"btn btn-outline-secondary\" type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\">\n <i class=\"bi bi-x\"></i>\n </button>\n {{/searchValue}}\n </div>\n </div>\n `;\n }\n\n /**\n * Build filter dropdown template\n */\n buildFilterDropdownTemplate() {\n const hasFilters = (this.filters && Object.keys(this.filters).length > 0) ||\n (this.additionalFilters && this.additionalFilters.length > 0);\n\n if (!hasFilters) {\n return '';\n }\n\n return `\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"bi bi-filter me-1\"></i>\n <span class=\"d-none d-lg-inline\">Add Filter</span>\n </button>\n <div class=\"dropdown-menu\" style=\"min-width: 250px;\">\n ${this.buildFilterList()}\n </div>\n </div>\n `;\n }\n\n /**\n * Build simple filter selection list\n */\n buildFilterList() {\n const allFilters = this.getAllAvailableFilters();\n const activeFilters = this.getActiveFilters();\n\n if (allFilters.length === 0) {\n return '<div class=\"dropdown-item-text text-muted\">No filters available</div>';\n }\n\n const filterItems = allFilters.map(filter => {\n const isActive = activeFilters.hasOwnProperty(filter.key);\n const activeClass = isActive ? 'active' : '';\n const icon = this.getFilterIcon(filter.type || filter.config?.type);\n\n return `\n <button class=\"dropdown-item ${activeClass}\"\n data-action=\"add-filter\"\n data-filter-key=\"${filter.key}\">\n <i class=\"bi bi-${icon} me-2\"></i>\n ${filter.label}\n ${isActive ? '<i class=\"bi bi-check-circle ms-auto\"></i>' : ''}\n </button>\n `;\n }).join('');\n\n return `\n ${filterItems}\n ${Object.keys(activeFilters).length > 0 ? `\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item text-danger\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-2\"></i>Clear All Filters\n </button>\n ` : ''}\n `;\n }\n\n /**\n * Update filter pills in the DOM\n */\n updateFilterPills() {\n const container = this.element?.querySelector('[data-container=\"filter-pills\"]');\n\n if (!container) {\n return;\n }\n\n const activeFilters = this.getActiveFilters();\n\n const pillsHTML = this.buildActivePills();\n container.innerHTML = pillsHTML;\n }\n\n /**\n * Update search input value across all search inputs\n */\n updateSearchInputs(value) {\n const searchInputs = this.element?.querySelectorAll('[data-filter=\"search\"]');\n if (searchInputs) {\n searchInputs.forEach(input => {\n input.value = value || '';\n });\n }\n }\n\n /**\n * Build active filter pills display\n */\n buildActivePills() {\n if (this.hideActivePills) {\n return '';\n }\n\n const activeFilters = this.getActiveFilters();\n const hasSearch = activeFilters.search && activeFilters.search.toString().trim() !== '';\n let filterEntries = Object.entries(activeFilters).filter(([key, value]) =>\n value && value.toString().trim() !== '' && key !== 'search'\n );\n\n // Hide specific pills based on configuration\n if (this.hideActivePillNames && this.hideActivePillNames.length > 0) {\n filterEntries = filterEntries.filter(([key]) =>\n !this.hideActivePillNames.includes(key)\n );\n }\n\n if (filterEntries.length === 0 && !hasSearch) {\n return '';\n }\n\n const pills = filterEntries.map(([paramKey, value]) => {\n const { field } = parseFilterKey(paramKey);\n const label = this.getFilterLabel(field);\n const displayText = formatFilterDisplay(paramKey, value, label);\n const icon = 'filter'; // search won't appear as pill anymore\n\n return `\n <span class=\"badge bg-primary me-1 mb-1 py-1 px-2 position-relative\" style=\"font-size: 0.75rem;\">\n <i class=\"bi bi-${icon} me-1\" style=\"font-size: 0.65rem;\"></i>\n\n <button type=\"button\" class=\"btn btn-link text-white p-0 ms-1\"\n style=\"font-size: 0.65rem; line-height: 1;\"\n data-action=\"edit-filter\"\n data-filter=\"${paramKey}\"\n title=\"Edit filter\">\n ${displayText}\n </button>\n\n <button type=\"button\" class=\"btn-close btn-close-white ms-1\"\n style=\"font-size: 0.6rem; width: 0.5rem; height: 0.5rem;\"\n data-action=\"remove-filter\"\n data-filter=\"${paramKey}\"\n title=\"Remove filter\">\n </button>\n </span>\n `;\n }).join('');\n\n // Show Clear All if there are multiple filters, or any filter + search\n const showClearAll = filterEntries.length > 1 || (filterEntries.length > 0 && hasSearch) || (filterEntries.length === 0 && hasSearch);\n const clearAllButton = showClearAll ? `\n <button class=\"btn btn-sm btn-outline-secondary mb-1 py-0 px-2\" style=\"font-size: 0.75rem;\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-1\" style=\"font-size: 0.7rem;\"></i>\n <small>Clear All</small>\n </button>\n ` : '';\n\n return `\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"d-flex flex-wrap align-items-center\">\n ${pills}\n ${clearAllButton}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Build table header template\n */\n buildTableHeaderTemplate() {\n let headerCells = '';\n\n // Selection checkbox header\n if (this.isSelectable()) {\n headerCells += `\n <th style=\"width: 40px; padding: 0;\">\n <div class=\"mojo-select-all-cell\" data-action=\"select-all\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </th>\n `;\n }\n\n // Column headers\n this.columns.forEach(column => {\n // Parse column key to get field name without pipes/formatters\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const sortable = this.sortable && column.sortable !== false;\n const currentSort = this.getSortBy() === fieldKey ? this.getSortDirection() : null;\n const sortIcon = this.getSortIcon(currentSort);\n const label = column.label || column.title || fieldKey;\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n const sortDropdown = sortable ? `\n <div class=\"dropdown d-inline-block ms-2\">\n <button class=\"btn btn-sm btn-link p-0 text-decoration-none\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\"\n data-column=\"${fieldKey}\">\n ${sortIcon}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n <li><a class=\"dropdown-item ${currentSort === 'asc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"asc\">\n <i class=\"bi bi-sort-alpha-down me-2\"></i>Sort A-Z\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === 'desc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"desc\">\n <i class=\"bi bi-sort-alpha-down-alt me-2\"></i>Sort Z-A\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === null ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"none\">\n <i class=\"bi bi-x-circle me-2\"></i>No Sort\n </a></li>\n </ul>\n </div>\n ` : '';\n\n headerCells += `\n <th class=\"${sortable ? 'sortable' : ''} ${responsiveClasses}\">\n <div class=\"d-flex align-items-center\">\n <span>${label}</span>\n ${sortDropdown}\n </div>\n </th>\n `;\n });\n\n // Actions header\n if (this.actions) {\n headerCells += '<th>Actions</th>';\n } else if (this.contextMenu) {\n headerCells += '<th style=\"width: 1px;\"></th>';\n }\n\n return `\n <thead>\n <tr>\n ${headerCells}\n </tr>\n </thead>\n `;\n }\n\n /**\n * Build table footer template with totals\n */\n buildTableFooterTemplate() {\n let footerCells = '';\n\n // Selection checkbox footer (empty)\n if (this.isSelectable()) {\n footerCells += '<td></td>';\n }\n\n // Column footers\n let totalColumnIndex = 0;\n this.columns.forEach((column, index) => {\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n if (column.footer_total) {\n // Use safe key for Mustache template\n const safeKey = `col_${totalColumnIndex}`;\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n\n let cellContent;\n if (formatter && typeof formatter === 'string') {\n cellContent = `{{{footerTotals.${safeKey}.value|${formatter}}}}`;\n } else {\n cellContent = `{{footerTotals.${safeKey}.value}}`;\n }\n\n footerCells += `<td class=\"table-footer-total ${responsiveClasses}\" data-total-column=\"${safeKey}\">${cellContent}</td>`;\n totalColumnIndex++;\n } else if (index === 0) {\n // First column shows \"Totals\" label\n footerCells += `<td class=\"table-footer-label ${responsiveClasses}\"><strong>Totals</strong></td>`;\n } else {\n // Empty cell for non-total columns\n footerCells += `<td class=\"${responsiveClasses}\"></td>`;\n }\n });\n\n // Actions footer (empty)\n if (this.actions) {\n footerCells += '<td></td>';\n } else if (this.contextMenu) {\n footerCells += '<td></td>';\n }\n\n return `\n <tfoot>\n <tr class=\"table-totals-row\">\n ${footerCells}\n </tr>\n </tfoot>\n `;\n }\n\n /**\n * Build batch actions panel\n */\n buildBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) {\n return '';\n }\n\n if (this.batchBarLocation === 'top') {\n // Toolbar-style batch actions for top placement\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"batch-${action.action}\" title=\"${action.label}\">\n <i class=\"${action.icon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${action.label}</span>\n </button>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel-top alert alert-info d-none mb-3\" role=\"alert\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"me-2\">\n <span class=\"batch-select-count\">0</span> ${this.options.batchPanelTitle || 'items'} selected\n </strong>\n </div>\n <div class=\"d-flex gap-2 align-items-center\">\n ${actionsHTML}\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"clear-selection\" title=\"Clear Selection\">\n <i class=\"bi bi-x-circle me-1\"></i>\n <span class=\"d-none d-lg-inline\">Clear</span>\n </button>\n </div>\n </div>\n </div>\n `;\n } else {\n // Original bottom panel style\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <div class=\"batch-select-action text-center px-2\" data-action=\"batch-${action.action}\">\n <div class=\"batch-action-icon fs-3\">\n <i class=\"${action.icon}\"></i>\n </div>\n <div class=\"batch-action-title small\">${action.label}</div>\n </div>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel rounded-start rounded-end\" style=\"display: none;\">\n <div class=\"batch-select-panel rounded-start rounded-end\">\n <div class=\"row g-0\">\n <div class=\"col-auto\">\n <div class=\"batch-select-count rounded-start\">0</div>\n </div>\n <div class=\"col\">\n <div class=\"ps-2 batch-select-title\">${this.options.batchPanelTitle || 'Rows'}</div>\n </div>\n <div class=\"col\">\n <div class=\"batch-select-actions d-flex justify-content-end\">\n ${actionsHTML}\n </div>\n </div>\n <div class=\"col-auto\">\n <div class=\"batch-select-end rounded-end\"></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n }\n\n /**\n * Build pagination template\n */\n buildPaginationTemplate() {\n if (!this.paginated) {\n return '';\n }\n\n return `\n <div class=\"table-status-bar mt-3\">\n <div class=\"d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3\">\n <div class=\"d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start\">\n <span class=\"text-muted\">\n Showing <span data-value=\"start\">0</span> to <span data-value=\"end\">0</span>\n of <span data-value=\"total\">0</span> entries\n </span>\n <div class=\"d-flex align-items-center\">\n <label class=\"form-label me-2 mb-0\">Show:</label>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\" data-change-action=\"page-size\">\n <option value=\"5\">5</option>\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n </div>\n <nav aria-label=\"Table pagination\">\n <ul class=\"pagination pagination-sm mb-0 justify-content-center\" data-container=\"pagination\">\n <!-- Pagination will be rendered here -->\n </ul>\n </nav>\n </div>\n </div>\n `;\n }\n\n /**\n * Override _createItemView to pass table-specific options\n */\n _createItemView(model, index) {\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n tableView: this, // Also pass as tableView for clarity\n template: this.itemTemplate,\n columns: this.columns,\n actions: this.actions,\n contextMenu: this.contextMenu,\n batchActions: this.batchActions,\n containerId: 'items'\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', (event) => {\n this._onItemSelect(event);\n this.updateBatchActionsPanel();\n });\n itemView.on('item:deselect', (event) => {\n this._onItemDeselect(event);\n this.updateBatchActionsPanel();\n });\n\n // Table-specific row events\n itemView.on('row:click', this._onRowClick.bind(this));\n itemView.on('row:view', this._onRowView.bind(this));\n itemView.on('row:edit', this._onRowEdit.bind(this));\n itemView.on('row:delete', this._onRowDelete.bind(this));\n itemView.on('cell:edit', this._onCellEdit.bind(this));\n itemView.on('cell:save', this._onCellSave.bind(this));\n itemView.on('cell:cancel', this._onCellCancel.bind(this));\n\n return itemView;\n }\n\n /**\n * Override onMounted to ensure filter pills are shown on initial load\n */\n async onMounted() {\n await super.onMounted();\n const activeFilters = this.getActiveFilters();\n\n // Ensure filter pills are displayed if there are active filters from URL\n if (this.collection && Object.keys(activeFilters).length > 0) {\n this.updateFilterPills();\n }\n\n // Add listener for native search clear button\n this.setupSearchClearListener();\n }\n\n /**\n * Setup listener for native search clear (X) button\n */\n setupSearchClearListener() {\n if (!this.element) return;\n\n const searchInputs = this.element.querySelectorAll('input[type=\"search\"][data-filter=\"search\"]');\n searchInputs.forEach(input => {\n // Listen for input event to detect native clear\n input.addEventListener('input', (event) => {\n // If value is empty and we had a search before, it was cleared\n if (event.target.value === '' && this.getActiveFilters().search) {\n this.onActionClearSearch(event, event.target);\n }\n });\n });\n }\n\n /**\n * Handle row click event\n */\n _onRowClick(event) {\n this.emit('row:click', event);\n\n // Default behavior - show item details if configured\n if (this.options.onRowClick) {\n return this.options.onRowClick(event.model, event.event);\n }\n\n if (this.clickAction === 'view') {\n this._onRowView(event);\n } else if (this.clickAction === 'edit') {\n this._onRowEdit(event);\n }\n }\n\n /**\n * Get the Model class from collection or instance\n */\n getModelClass(model) {\n // Try to get from collection first\n if (this.collection?.ModelClass) return this.collection.ModelClass;\n if (this.collection?.model) return this.collection.model;\n\n // Try to get from a model instance\n if (model?.constructor) return model.constructor;\n\n // Return null if we can't determine\n return null;\n }\n\n /**\n * Get model name for display\n */\n getModelName(model) {\n const ModelClass = this.getModelClass(model);\n if (!ModelClass) return 'Item';\n\n return ModelClass.MODEL_NAME ||\n ModelClass.name.replace(/Model$/, '') ||\n 'Item';\n }\n\n /**\n * Resolve item view class with fallbacks\n */\n getItemViewClass(model) {\n // Check instance options first\n if (this.itemView) return this.itemView;\n\n // Check Model class static property\n const ModelClass = this.getModelClass(model);\n if (ModelClass?.VIEW_CLASS) return ModelClass.VIEW_CLASS;\n\n return null; // Will use data view as fallback\n }\n\n /**\n * Resolve add form configuration with fallbacks\n */\n getAddFormConfig(ModelClass) {\n return this.addForm ||\n ModelClass?.ADD_FORM ||\n this.editForm ||\n ModelClass?.EDIT_FORM;\n }\n\n /**\n * Resolve edit form configuration with fallbacks\n */\n getEditFormConfig(ModelClass) {\n return this.editForm ||\n ModelClass?.EDIT_FORM ||\n this.addForm ||\n ModelClass?.ADD_FORM;\n }\n\n /**\n * Get form dialog configuration\n */\n getFormDialogConfig(ModelClass) {\n return {\n ...ModelClass?.FORM_DIALOG_CONFIG,\n ...this.formDialogConfig\n };\n }\n\n /**\n * Render a template string with model context\n */\n renderTemplateString(template, model) {\n if (!template) return '';\n\n // Use Mustache to render the template with the model as context\n return Mustache.render(template, model);\n }\n\n /**\n * Handle row view action\n */\n async _onRowView(event) {\n this.emit('row:view', event);\n\n // Check for custom handler first\n if (this.options.onItemView) {\n await this.options.onItemView(event.model, event.event);\n return;\n }\n\n const ViewClass = this.getItemViewClass(event.model);\n\n if (ViewClass) {\n // Use custom view class\n const viewInstance = new ViewClass({ model: event.model });\n await Dialog.showDialog({\n header: false,\n body: viewInstance,\n size: 'lg',\n centered: false,\n ...this.getFormDialogConfig(this.getModelClass(event.model)),\n ...this.viewDialogOptions\n });\n } else {\n // Fallback to data view\n await Dialog.showData({\n title: `View ${this.getModelName(event.model)} #${event.model.id}`,\n model: event.model\n });\n }\n }\n\n /**\n * Handle row edit action\n */\n async _onRowEdit(event) {\n this.emit('row:edit', event);\n\n // Check for custom handler first\n if (this.options.onItemEdit) {\n await this.options.onItemEdit(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n let formConfig = this.getEditFormConfig(ModelClass);\n\n if (formConfig) {\n if (!formConfig.fields) {\n formConfig = { title: `Edit ${this.getModelName(event.model)}`, fields: formConfig };\n }\n\n const result = await Dialog.showModelForm({\n model: event.model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (!result) return; // Cancelled\n\n if (!result.success || !result?.result?.data.status) {\n Dialog.showError(result?.result?.data?.error || result?.result?.message || \"An error occurred\");\n return;\n }\n\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const result = await Dialog.showDialog({\n title: `Edit ${this.getModelName(event.model)} #${event.model.id}`,\n body: new FormView({\n model: event.model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await event.model.save(result);\n if (!resp.data?.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle row delete action\n */\n async _onRowDelete(event) {\n this.emit('row:delete', event);\n\n // Check for custom handler first\n if (this.options.onItemDelete) {\n await this.options.onItemDelete(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n\n // Get delete template from options, Model class, or use default\n const template = this.deleteTemplate ||\n ModelClass?.DELETE_TEMPLATE ||\n 'Are you sure you want to delete this {{name||\"item\"}}?';\n\n // Render template with model context\n const message = this.renderTemplateString(template, event.model);\n\n const confirmed = await Dialog.confirm({\n message: message || 'Are you sure you want to delete this item?',\n title: 'Confirm Delete',\n confirmText: 'Delete',\n confirmClass: 'btn-danger'\n });\n\n if (confirmed) {\n await event.model.destroy();\n this.collection.fetch();\n }\n }\n\n /**\n * Handle cell edit event\n */\n _onCellEdit(event) {\n this.emit('cell:edit', event);\n }\n\n /**\n * Handle cell save event\n */\n async _onCellSave(event) {\n this.emit('cell:save', event);\n // Model save is now handled directly in TableRow.saveCellEdit()\n }\n\n /**\n * Handle cell cancel event\n */\n _onCellCancel(event) {\n this.emit('cell:cancel', event);\n }\n\n /**\n * Check if fullscreen is supported by the browser\n */\n isFullscreenSupported() {\n return !!(\n document.fullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.msFullscreenEnabled\n );\n }\n\n /**\n * Handle toggle fullscreen action\n */\n async onActionToggleFullscreen(event, element) {\n if (this.isFullscreen) {\n await this.exitFullscreen();\n } else {\n await this.enterFullscreen();\n }\n }\n\n /**\n * Enter fullscreen mode\n */\n async enterFullscreen() {\n try {\n // Use browser's native fullscreen API\n if (this.element.requestFullscreen) {\n await this.element.requestFullscreen();\n } else if (this.element.mozRequestFullScreen) {\n await this.element.mozRequestFullScreen();\n } else if (this.element.webkitRequestFullscreen) {\n await this.element.webkitRequestFullscreen();\n } else if (this.element.msRequestFullscreen) {\n await this.element.msRequestFullscreen();\n }\n\n this.isFullscreen = true;\n this.element.classList.add('table-fullscreen');\n this.updateFullscreenButton();\n\n // Listen for fullscreen change events\n this.setupFullscreenListeners();\n\n this.emit('table:fullscreen:enter');\n\n } catch (error) {\n console.warn('Could not enter fullscreen:', error);\n }\n }\n\n /**\n * Exit fullscreen mode\n */\n async exitFullscreen() {\n try {\n if (document.exitFullscreen) {\n await document.exitFullscreen();\n } else if (document.mozCancelFullScreen) {\n await document.mozCancelFullScreen();\n } else if (document.webkitExitFullscreen) {\n await document.webkitExitFullscreen();\n } else if (document.msExitFullscreen) {\n await document.msExitFullscreen();\n }\n\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n\n this.emit('table:fullscreen:exit');\n\n } catch (error) {\n console.warn('Could not exit fullscreen:', error);\n }\n }\n\n /**\n * Update fullscreen button icon and title\n */\n updateFullscreenButton() {\n const button = this.element?.querySelector('.btn-fullscreen');\n const icon = button?.querySelector('i');\n\n if (button && icon) {\n if (this.isFullscreen) {\n icon.className = 'bi bi-fullscreen-exit';\n button.title = 'Exit Fullscreen';\n } else {\n icon.className = 'bi bi-fullscreen';\n button.title = 'Enter Fullscreen';\n }\n }\n }\n\n /**\n * Setup fullscreen event listeners\n */\n setupFullscreenListeners() {\n // Don't add listeners multiple times\n if (this._fullscreenHandler) return;\n\n const handleFullscreenChange = () => {\n const isCurrentlyFullscreen = !!(\n document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement ||\n document.msFullscreenElement\n );\n\n if (!isCurrentlyFullscreen && this.isFullscreen) {\n // User exited fullscreen via ESC or browser controls\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n this.emit('table:fullscreen:exit');\n }\n };\n\n // Add listeners for all browser prefixes\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n document.addEventListener('msfullscreenchange', handleFullscreenChange);\n\n // Store handler for cleanup\n this._fullscreenHandler = handleFullscreenChange;\n }\n\n /**\n * Cleanup fullscreen listeners\n */\n cleanupFullscreenListeners() {\n if (this._fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this._fullscreenHandler);\n document.removeEventListener('mozfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('webkitfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('msfullscreenchange', this._fullscreenHandler);\n this._fullscreenHandler = null;\n }\n }\n\n /**\n * Override destroy to cleanup fullscreen listeners\n */\n destroy() {\n this.cleanupFullscreenListeners();\n super.destroy();\n }\n\n /**\n * Handle refresh action\n */\n async onActionRefresh(event, element) {\n await this.refresh();\n }\n\n /**\n * Handle add action\n */\n async onActionAdd(event, element) {\n // Check for custom handler first - if provided, just emit event and let handler deal with it\n if (this.options.onAdd) {\n this.emit('table:add', { event });\n await this.options.onAdd(event);\n return;\n }\n\n // Emit event for external listeners\n this.emit('table:add', { event });\n\n const ModelClass = this.getModelClass();\n if (!ModelClass) {\n console.warn('Cannot determine Model class for add operation');\n return;\n }\n\n let formConfig = this.getAddFormConfig(ModelClass);\n\n if (formConfig) {\n const model = new ModelClass();\n if (!formConfig.fields) {\n formConfig = { title: `Add ${this.getModelName()}`, fields: formConfig };\n }\n\n const result = await Dialog.showForm({\n model: model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (result) {\n if (this.options.addRequiresActiveGroup) {\n result.group = this.getApp().activeGroup.id;\n }\n if (this.options.addRequiresActiveUser) {\n result.user = this.getApp().activeUser.id;\n }\n if (this.options.addFormDefaults) {\n Object.assign(result, this.options.addFormDefaults);\n }\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp?.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const model = new ModelClass();\n\n const result = await Dialog.showDialog({\n title: `Add ${this.getModelName()}`,\n body: new FormView({\n model: model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle export action\n */\n async onActionExport(event, element) {\n const format = element.getAttribute('data-format') || 'json';\n\n this.emit('table:export', {\n format: format,\n source: this.exportSource,\n event\n });\n\n if (this.exportSource === 'remote') {\n if (this.collection) {\n await this.collection.download(format);\n } else {\n console.warn('TableView: Cannot export from remote without a collection.');\n }\n } else {\n // Handle local export (future enhancement)\n if (this.options.onExport) {\n await this.options.onExport(this.collection?.toJSON() || [], format);\n } else {\n console.warn('TableView: onExport handler not implemented for local export.');\n }\n }\n }\n\n /**\n * Handle search action (Enter key triggers this via EventDelegate)\n */\n async onActionApplySearch(event, element) {\n const searchTerm = element.value.trim();\n\n if (this.collection) {\n this.setFilter('search', searchTerm);\n\n // Reset to first page when searching\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side filtering\n this.render();\n }\n }\n\n // Update filter pills when search changes\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear search button\n */\n async onActionClearSearch(event, element) {\n // Clear the search filter\n this.setFilter('search', null);\n\n // Reset to first page\n if (this.collection) {\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n }\n\n // Render will rebuild the search input with empty value\n await this.render();\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm: '', event });\n this.emit('params-changed');\n }\n\n /**\n * Get current sort field\n */\n getSortBy() {\n const sort = this.collection?.params?.sort;\n if (!sort) return null;\n return sort.startsWith('-') ? sort.slice(1) : sort;\n }\n\n /**\n * Get current sort direction\n */\n getSortDirection() {\n const sort = this.collection?.params?.sort;\n if (!sort) return 'asc';\n return sort.startsWith('-') ? 'desc' : 'asc';\n }\n\n /**\n * Get sort icon based on current sort direction\n */\n getSortIcon(direction) {\n if (direction === 'asc') {\n return '<i class=\"bi bi-sort-alpha-down text-primary\"></i>';\n } else if (direction === 'desc') {\n return '<i class=\"bi bi-sort-alpha-down-alt text-primary\"></i>';\n } else {\n return '<i class=\"bi bi-three-dots-vertical text-muted\"></i>';\n }\n }\n\n /**\n * Handle sort action\n */\n async onActionSort(event, element) {\n event.preventDefault();\n const field = element.getAttribute('data-field');\n const direction = element.getAttribute('data-direction');\n\n if (this.collection) {\n let newSort;\n\n if (direction === 'none') {\n newSort = undefined; // Remove sort\n } else if (direction === 'desc') {\n newSort = `-${field}`; // Descending sort\n } else {\n newSort = field; // Ascending sort\n }\n\n this.collection.setParams({\n ...this.collection.params,\n sort: newSort,\n start: 0 // Reset to first page when sorting changes\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side sorting\n if (newSort) {\n const desc = newSort.startsWith('-');\n const sortField = desc ? newSort.slice(1) : newSort;\n\n this.collection.sort((a, b) => {\n const aVal = a.get(sortField);\n const bVal = b.get(sortField);\n\n if (aVal < bVal) return desc ? 1 : -1;\n if (aVal > bVal) return desc ? -1 : 1;\n return 0;\n });\n }\n\n this.render();\n }\n }\n\n // Update sort icons in the DOM\n this.updateSortIcons();\n\n this.emit('table:sort', { field, event });\n this.emit('params-changed');\n }\n\n /**\n * Update sort icons in all column headers\n */\n updateSortIcons() {\n if (!this.element) return;\n\n const currentSortField = this.getSortBy();\n const currentSortDir = this.getSortDirection();\n\n // Update all sort dropdown buttons\n this.columns.forEach(column => {\n if (this.sortable && column.sortable !== false) {\n // Parse the column key to get just the field name (without pipes/formatters)\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const dropdown = this.element.querySelector(`[data-bs-toggle=\"dropdown\"][data-column=\"${fieldKey}\"]`);\n if (dropdown) {\n const isSorted = currentSortField === fieldKey;\n const sortIcon = this.getSortIcon(isSorted ? currentSortDir : null);\n dropdown.innerHTML = sortIcon;\n\n // Update dropdown menu items\n const dropdownMenu = dropdown.nextElementSibling;\n if (dropdownMenu) {\n const ascItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"asc\"]`);\n const descItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"desc\"]`);\n const noneItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"none\"]`);\n\n if (ascItem) {\n ascItem.classList.toggle('active', isSorted && currentSortDir === 'asc');\n }\n if (descItem) {\n descItem.classList.toggle('active', isSorted && currentSortDir === 'desc');\n }\n if (noneItem) {\n noneItem.classList.toggle('active', !isSorted || currentSortField !== fieldKey);\n }\n }\n }\n }\n });\n }\n\n /**\n * Handle select all action\n */\n async onActionSelectAll(event, element) {\n event.stopPropagation();\n const isCurrentlyAllSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n\n if (!isCurrentlyAllSelected) {\n // Select all visible items\n this.forEachItem(itemView => {\n if (!itemView.selected) {\n itemView.select();\n }\n });\n } else {\n // Deselect all\n this.clearSelection();\n }\n\n // Update select all checkbox visual state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n selectAllCell.classList.toggle('selected', !isCurrentlyAllSelected);\n }\n\n // Update batch actions panel\n this.updateBatchActionsPanel();\n }\n\n /**\n * Override onBeforeRender to set data properties before rendering\n */\n async onBeforeRender() {\n // Set properties that Mustache needs\n this.searchValue = this.getActiveFilters().search || '';\n this.footerTotals = this.calculateFooterTotals();\n }\n\n /**\n * Override onAfterRender to update pagination info\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Update footer totals in case collection loaded after initial render\n if (this.hasFooterTotals) {\n this.updateFooterTotals();\n }\n\n // Update pagination info\n if (this.paginated && this.collection) {\n const total = this.collection.meta?.count || this.collection.length();\n const start = this.collection.params?.start || 0;\n const size = this.collection.params?.size || 10;\n const end = Math.min(start + size, total);\n\n const startEl = this.element.querySelector('[data-value=\"start\"]');\n const endEl = this.element.querySelector('[data-value=\"end\"]');\n const totalEl = this.element.querySelector('[data-value=\"total\"]');\n\n if (startEl) startEl.textContent = start + 1;\n if (endEl) endEl.textContent = end;\n if (totalEl) totalEl.textContent = total;\n\n // Update page size selector\n const pageSizeSelect = this.element.querySelector('[data-change-action=\"page-size\"]');\n if (pageSizeSelect) {\n pageSizeSelect.value = size;\n }\n\n // Render pagination controls\n this.renderPagination();\n }\n\n // Update sort icons after render\n this.updateSortIcons();\n\n // Update filter pills after render - this is crucial for showing pills on page load\n this.updateFilterPills();\n\n // Re-setup search clear listener after render\n this.setupSearchClearListener();\n }\n\n /**\n * Render pagination controls\n * - Prev/Next wrap around (never disabled)\n * - Truncated page list with first/last and ellipses\n */\n renderPagination() {\n const paginationContainer = this.element.querySelector('[data-container=\"pagination\"]');\n if (!paginationContainer || !this.collection) return;\n\n const total = this.collection.meta?.count || this.collection.length();\n const size = this.collection.params?.size || 10;\n const start = this.collection.params?.start || 0;\n const currentPage = Math.floor(start / size) + 1;\n const totalPages = Math.ceil(total / size);\n\n if (totalPages <= 1) {\n paginationContainer.innerHTML = '';\n return;\n }\n\n const prevPage = currentPage > 1 ? currentPage - 1 : totalPages;\n const nextPage = currentPage < totalPages ? currentPage + 1 : 1;\n\n const pages = [];\n\n // Previous (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${prevPage}\">\n <i class=\"bi bi-chevron-left\"></i>\n </a>\n </li>\n `);\n\n // Build truncated page list: always show 1 and totalPages, with neighbors around current\n const neighbors = 1; // how many pages to show on each side of current\n const visibleSet = new Set([1, totalPages]);\n for (let i = currentPage - neighbors; i <= currentPage + neighbors; i++) {\n if (i >= 1 && i <= totalPages) visibleSet.add(i);\n }\n const visible = Array.from(visibleSet).sort((a, b) => a - b);\n\n // Render pages with ellipses where there are gaps\n let last = 0;\n for (const p of visible) {\n if (last && p - last > 1) {\n // gap -> ellipsis\n pages.push(`\n <li class=\"page-item disabled\"><span class=\"page-link\">…</span></li>\n `);\n }\n pages.push(`\n <li class=\"page-item ${p === currentPage ? 'active' : ''}\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${p}\">${p}</a>\n </li>\n `);\n last = p;\n }\n\n // Next (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${nextPage}\">\n <i class=\"bi bi-chevron-right\"></i>\n </a>\n </li>\n `);\n\n paginationContainer.innerHTML = pages.join('');\n }\n\n /**\n * Handle page change\n * - Normalizes and wraps page number (1..totalPages)\n */\n async onActionPage(event, element) {\n event.preventDefault();\n\n const rawPage = parseInt(element.getAttribute('data-page'), 10);\n const size = this.collection.params?.size || 10;\n const total = this.collection.meta?.count || this.collection.length();\n const totalPages = Math.max(1, Math.ceil(total / size));\n\n let page = isNaN(rawPage) ? 1 : rawPage;\n if (page < 1) page = totalPages;\n if (page > totalPages) page = 1;\n\n this.collection.setParams({\n ...this.collection.params,\n start: (page - 1) * size\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n this.render();\n }\n\n this.emit('table:page', { page, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle page size change\n */\n async onChangePageSize(event, element) {\n const newSize = parseInt(element.value);\n\n if (this.collection) {\n // Reset to first page when changing page size\n this.collection.setParams({\n ...this.collection.params,\n start: 0,\n size: newSize\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n }\n\n this.emit('table:pagesize', { size: newSize, event });\n this.emit('params-changed');\n }\n\n /**\n * Get active filters from collection params\n */\n getActiveFilters() {\n if (!this.collection?.params) {\n return {};\n }\n const { start, size, sort, ...allParams } = this.collection.params;\n const filters = {};\n\n // Reconstruct daterange filters from their component parts\n const processedKeys = new Set();\n\n // First pass: identify and process daterange filters\n const allFilterConfigs = this.getAllAvailableFilters();\n allFilterConfigs.forEach(filterDef => {\n if (filterDef.config.type === 'daterange') {\n const key = filterDef.key;\n const startName = filterDef.config.startName || 'dr_start';\n const endName = filterDef.config.endName || 'dr_end';\n const fieldName = filterDef.config.fieldName || 'dr_field';\n\n // Check if this daterange filter is active for this specific key\n if (allParams[fieldName] === key && (allParams[startName] || allParams[endName])) {\n filters[key] = {\n start: allParams[startName] || '',\n end: allParams[endName] || ''\n };\n\n processedKeys.add(startName);\n processedKeys.add(endName);\n processedKeys.add(fieldName);\n }\n }\n });\n\n // Second pass: add remaining filters\n Object.keys(allParams).forEach(paramKey => {\n if (!processedKeys.has(paramKey)) {\n filters[paramKey] = allParams[paramKey];\n }\n });\n\n return filters;\n }\n\n /**\n * Set a filter value\n */\n setFilter(key, value) {\n if (!this.collection) return;\n\n const filterConfig = this.getFilterConfig(key);\n\n // Handle daterange filters specially\n if (filterConfig && filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n const fieldName = filterConfig.fieldName || 'dr_field';\n\n // Always remove old values first\n delete this.collection.params[startName];\n delete this.collection.params[endName];\n delete this.collection.params[fieldName];\n\n // Set new values if provided and not empty\n if (value && typeof value === 'object' && (value.start || value.end)) {\n if (value.start) this.collection.params[startName] = value.start;\n if (value.end) this.collection.params[endName] = value.end;\n this.collection.params[fieldName] = key;\n }\n } else {\n // Parse key to get field and lookup\n const { field, lookup } = parseFilterKey(key);\n \n // Clear old values - remove both base field and variants\n delete this.collection.params[key];\n delete this.collection.params[field];\n delete this.collection.params[`${field}__in`];\n \n if (!value || (Array.isArray(value) && value.length === 0)) {\n return; // Cleared\n }\n \n // Smart param generation for multiselect fields\n if (Array.isArray(value)) {\n if (value.length === 1) {\n // Single value from array - use simple key (no __in)\n this.collection.params[field] = value[0];\n } else {\n // Multiple values - use __in lookup\n this.collection.params[`${field}__in`] = value.join(',');\n }\n } else {\n // Single value - use key as-is (may include lookup like __gte)\n this.collection.params[key] = value;\n }\n }\n }\n\n /**\n * Get all available filters\n */\n getAllAvailableFilters() {\n const filters = [];\n\n // Add column-based filters\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n filters.push({\n key: fieldKey,\n label: column.filter.label || column.label || fieldKey,\n type: column.filter.type,\n config: column.filter\n });\n }\n });\n\n // Add additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n this.additionalFilters.forEach(filter => {\n filters.push({\n key: filter.name || filter.key,\n label: filter.label,\n type: filter.type,\n config: filter\n });\n });\n }\n\n return filters;\n }\n\n /**\n * Get filter configuration for a key\n */\n getFilterConfig(filterKey) {\n // Check column filters first\n const column = this.columns.find(col => {\n const { fieldKey } = this.parseColumnKey(col.key);\n return fieldKey === filterKey;\n });\n if (column && column.filter) {\n return column.filter;\n }\n\n // Check additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n const filter = this.additionalFilters.find(f => (f.name || f.key) === filterKey);\n if (filter) {\n return filter;\n }\n }\n\n return null;\n }\n\n /**\n * Get filter label\n */\n getFilterLabel(key) {\n if (key === 'search') return 'Search';\n\n const filter = this.filters[key];\n if (filter && filter.label) return filter.label;\n\n const additionalFilter = this.additionalFilters.find(f =>\n (f.name || f.key) === key\n );\n if (additionalFilter && additionalFilter.label) return additionalFilter.label;\n\n return key.charAt(0).toUpperCase() + key.slice(1);\n }\n\n /**\n * Get filter display value\n */\n getFilterDisplayValue(key, value) {\n if (key === 'search') return `\"${value}\"`;\n\n const filter = this.filters[key] ||\n this.additionalFilters.find(f => (f.name || f.key) === key);\n\n if (filter && filter.type === 'daterange' && typeof value === 'object') {\n const start = value.start || '';\n const end = value.end || '';\n return `${start} to ${end}`;\n }\n\n if (filter && filter.type === 'select' && filter.options) {\n if (typeof filter.options[0] === 'object') {\n const option = filter.options.find(opt => opt.value === value);\n return option ? option.label : value;\n }\n return value;\n }\n\n return value;\n }\n\n /**\n * Get icon for filter type\n */\n getFilterIcon(type) {\n const icons = {\n 'text': 'search',\n 'select': 'funnel',\n 'date': 'calendar',\n 'daterange': 'calendar-range',\n 'number': '123',\n 'boolean': 'toggle-on'\n };\n return icons[type] || 'filter';\n }\n\n /**\n * Handle add filter action\n */\n async onActionAddFilter(event, element) {\n const filterKey = element.getAttribute('data-filter-key');\n const filterConfig = this.getFilterConfig(filterKey);\n const currentValue = this.getActiveFilters()[filterKey];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey);\n return;\n }\n\n // Using statically imported Dialog\n\n // Show dialog for this specific filter\n const result = await Dialog.showForm({\n title: `${currentValue !== undefined && currentValue !== '' ? 'Edit' : 'Add'} ${this.getFilterLabel(filterKey)} Filter`,\n size: 'md',\n fields: [this.buildFilterDialogField(filterConfig, currentValue, filterKey)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n // Use the filter key for setFilter (it will handle the lookup internally)\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Build filter dialog field configuration\n */\n buildFilterDialogField(filterConfig, currentValue, filterKey) {\n const field = {\n name: 'filter_value',\n label: filterConfig.label,\n value: currentValue,\n ...filterConfig,\n // Ensure placeholder is passed through (support both casings)\n placeholder: filterConfig.placeholder || filterConfig.placeHolder\n };\n\n // Set current value appropriately based on filter type\n if (filterConfig.type === 'daterange') {\n // Apply defaults for daterange\n field.startName = field.startName || 'dr_start';\n field.endName = field.endName || 'dr_end';\n field.fieldName = field.fieldName || 'dr_field';\n field.format = field.format || 'YYYY-MM-DD';\n field.displayFormat = field.displayFormat || 'MMM DD, YYYY';\n field.separator = field.separator || ' to ';\n field.label = field.label || 'Date Range';\n\n // Handle daterange current values\n if (currentValue && typeof currentValue === 'object') {\n field.startDate = currentValue.start || '';\n field.endDate = currentValue.end || '';\n }\n } else if (filterConfig.type === 'multiselect') {\n // Convert comma-separated string to array for multiselect\n let valueArray = [];\n if (currentValue) {\n if (Array.isArray(currentValue)) {\n valueArray = currentValue;\n } else if (typeof currentValue === 'string') {\n // Split by comma and trim whitespace\n valueArray = currentValue.split(',').map(v => v.trim()).filter(v => v);\n }\n }\n \n field.value = valueArray;\n \n // Ensure placeholder is set (support both casings)\n if (!field.placeholder && !field.placeHolder) {\n if (filterConfig.placeholder || filterConfig.placeHolder) {\n field.placeholder = filterConfig.placeholder || filterConfig.placeHolder;\n } else if (filterConfig.label) {\n field.placeholder = `Select ${filterConfig.label}...`;\n }\n }\n }\n\n return field;\n }\n\n /**\n * Extract filter value from form result\n */\n extractFilterValue(filterConfig, formResult) {\n if (filterConfig.type === 'daterange') {\n // Extract start/end values based on naming convention\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n\n const result = {\n start: formResult[startName],\n end: formResult[endName]\n };\n\n return result;\n }\n\n if (filterConfig.type === 'multiselect') {\n // Return array as-is for multiselect\n return formResult.filter_value;\n }\n\n return formResult.filter_value;\n }\n\n /**\n * Apply filters to collection and refresh\n */\n async applyFilters() {\n // Reset to first page when filters change\n if (this.collection) {\n this.collection.params.start = 0;\n }\n\n // For REST collections, fetch data with new filters\n if (this.collection?.restEnabled) {\n try {\n await this.collection.fetch();\n this.render();\n } catch (error) {\n console.error('Failed to fetch filtered data:', error);\n this.render();\n }\n } else {\n this.render();\n }\n\n // Update filter pills display\n this.updateFilterPills();\n\n // Emit params changed event for URL synchronization\n this.emit('params-changed');\n }\n\n /**\n * Handle edit filter action from pill\n */\n async onActionEditFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n \n // Parse the key to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n \n // Try to get filter config using the parsed field name first, then original key\n let filterConfig = this.getFilterConfig(field) || this.getFilterConfig(filterKey);\n \n // Get current value - could be under filterKey or field\n const activeFilters = this.getActiveFilters();\n const currentValue = activeFilters[filterKey] || activeFilters[field];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey, 'or field:', field);\n return;\n }\n\n // Using statically imported Dialog\n\n // Show mini dialog for this specific filter\n const result = await Dialog.showForm({\n title: `Edit ${this.getFilterLabel(field)} Filter`,\n size: 'md',\n data: {filter_value: currentValue},\n fields: [this.buildFilterDialogField(filterConfig, currentValue, field)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Handle remove filter action\n */\n async onActionRemoveFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n \n // Parse to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n \n // Clear the filter using the original key\n this.setFilter(filterKey, null);\n\n // If removing search filter, clear search inputs\n if (filterKey === 'search') {\n this.updateSearchInputs('');\n }\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after removing\n this.updateFilterPills();\n\n this.emit('filter:remove', { key: filterKey, field });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear all filters action\n */\n async onActionClearAllFilters(event, element) {\n if (!this.collection) return;\n\n // Clear all filters except pagination and sorting\n const { start, size, sort } = this.collection.params;\n this.collection.params = { start, size };\n if (sort) this.collection.params.sort = sort;\n\n // Clear all search inputs\n this.updateSearchInputs('');\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after clearing\n this.updateFilterPills();\n\n this.emit('filters:clear');\n this.emit('params-changed');\n }\n\n /**\n * Update batch actions panel visibility and count\n */\n updateBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) return;\n\n const selectedCount = this.getSelectedItems().length;\n\n if (this.batchBarLocation === 'top') {\n // Handle top panel style\n const panel = this.element?.querySelector('.batch-actions-panel-top');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n\n // Use Bootstrap's d-none class for cleaner show/hide\n if (selectedCount > 0) {\n panel.classList.remove('d-none');\n } else {\n panel.classList.add('d-none');\n }\n }\n } else {\n // Handle bottom panel style (original)\n const panel = this.element?.querySelector('.batch-actions-panel');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n panel.style.display = selectedCount > 0 ? 'block' : 'none';\n }\n }\n\n // Update select all checkbox state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n const allSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n const someSelected = Array.from(this.itemViews.values()).some(item => item.selected);\n\n selectAllCell.classList.toggle('selected', allSelected);\n selectAllCell.classList.toggle('indeterminate', !allSelected && someSelected);\n\n // Update icon for indeterminate state\n const icon = selectAllCell.querySelector('i');\n if (icon) {\n icon.className = !allSelected && someSelected ? 'bi bi-dash' : 'bi bi-check';\n }\n }\n }\n\n /**\n * Handle batch action clicks\n */\n async onActionBatch(event, element) {\n const batchAction = element.getAttribute('data-action').replace('batch-', '');\n const selectedItems = this.getSelectedItems();\n\n this.emit('batch:action', {\n action: batchAction,\n items: selectedItems,\n event\n });\n }\n\n /**\n * Handle clear selection action (for top batch bar)\n */\n async onActionClearSelection(event, element) {\n this.clearSelection();\n this.updateBatchActionsPanel();\n }\n\n /**\n * Handle custom toolbar button with handler function\n */\n async onActionCustomToolbarButton(event, element) {\n const buttonIndex = parseInt(element.getAttribute('data-button-index'), 10);\n const button = this.toolbarButtons[buttonIndex];\n\n if (button && typeof button.handler === 'function') {\n await button.handler.call(this, event, element);\n }\n }\n}\n\nexport default TableView;\n","/**\n * TablePage - Page component that manages a TableView with URL parameter synchronization\n *\n * A clean, simplified implementation using the new TableView component.\n * Automatically syncs pagination, sorting, and filtering with URL parameters.\n *\n * @example\n * const usersPage = new TablePage({\n * pageName: 'users',\n * title: 'User Management',\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email' },\n * { key: 'role', label: 'Role', type: 'badge' }\n * ],\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport Page from '@core/Page.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport TableView from '@core/views/table/TableView.js';\nimport Collection from '@core/Collection.js';\n\nclass TablePage extends Page {\n constructor(options = {}) {\n super({\n ...options,\n pageName: options.pageName || options.name || 'table'\n });\n\n // Page configuration\n this.title = options.title || this.pageName;\n this.description = options.description || '';\n\n // Collection setup\n this.Collection = options.Collection || null;\n this.collection = options.collection || null;\n\n // our default collection query\n this.defaultQuery = options.defaultQuery || {};\n\n // Group field configuration - defaults to \"group\"\n this.groupField = options.groupField || 'group';\n\n // Store configuration for TableView\n // Map legacy property names to new ones\n this.tableViewConfig = {\n // Core table properties\n columns: options.columns || [],\n actions: options.actions || null,\n contextMenu: options.contextMenu || null,\n batchActions: options.batchActions || null,\n batchBarLocation: options.batchBarLocation || 'top',\n clickAction: options.clickAction || 'view',\n // Map legacy form properties to new names\n addForm: options.addForm || options.formFields || options.formCreate,\n editForm: options.editForm || options.formEdit || options.formFields,\n\n // Model operation configurations\n itemView: options.itemView,\n deleteTemplate: options.deleteTemplate,\n formDialogConfig: options.formDialogConfig,\n viewDialogOptions: options.viewDialogOptions,\n\n // Features\n searchable: options.searchable !== false,\n sortable: options.sortable !== false,\n filterable: options.filterable !== false,\n paginated: options.paginated !== false,\n\n // Selection mode\n selectionMode: options.selectionMode || (options.selectable ? 'multiple' : 'none'),\n\n // Filter configuration\n filters: options.filters || options.additionalFilters || [],\n hideActivePills: options.hideActivePills || false,\n hideActivePillNames: options.hideActivePillNames || [],\n searchPlacement: options.searchPlacement || 'toolbar',\n\n // Display options for the HTML table element\n tableOptions: {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n ...options.tableOptions\n },\n\n // Additional options\n emptyMessage: options.emptyMessage || 'No data available',\n searchPlaceholder: options.searchPlaceholder || 'Search...',\n showAdd: options.showAdd !== false,\n showExport: options.showExport !== false,\n\n // Custom handlers\n onItemView: options.onItemView,\n onItemEdit: options.onItemEdit,\n onItemDelete: options.onItemDelete,\n onAdd: options.onAdd,\n onExport: options.onExport,\n\n // Override with tableViewOptions if provided\n ...options.tableViewOptions\n };\n\n // URL synchronization\n this.urlSyncEnabled = options.urlSyncEnabled !== false;\n\n // Status tracking\n this.lastUpdated = null;\n this.isLoading = false;\n\n // Set up template\n this.template = options.template || this.buildTemplate();\n }\n\n /**\n * Build the page template\n */\n buildTemplate() {\n return `\n <div class=\"table-page-container\">\n\n <div class=\"table-container\" data-container=\"table\"></div>\n\n {{#showStatus}}\n <div class=\"table-status-bar table-status-top\">\n <div class=\"status-info\">\n <div class=\"d-flex justify-content-between w-100\">\n <span class=\"text-muted\">\n <i class=\"bi bi-clock me-1\"></i>\n Last updated: <span data-status=\"last-updated\">{{lastUpdated}}</span>\n </span>\n <span class=\"text-muted\">\n <i class=\"bi bi-list-ol me-1\"></i>\n Total records: <span data-status=\"record-count\">0</span>\n </span>\n </div>\n </div>\n </div>\n {{/showStatus}}\n\n </div>\n `;\n }\n\n /**\n * Initialize the page\n */\n async onInit() {\n await super.onInit();\n\n // Create collection if needed\n if (!this.collection) {\n if (this.Collection) {\n this.collection = new this.Collection();\n } else {\n this.collection = new Collection();\n }\n }\n\n // Apply URL query parameters to collection\n this.applyQueryToCollection();\n\n // Create TableView instance with all configuration\n this.tableView = new TableView({\n collection: this.collection,\n containerId: 'table',\n fetchOnMount: true,\n ...this.tableViewConfig\n });\n\n // Add as child view\n this.addChild(this.tableView);\n\n // Set up event listeners\n this.setupEventListeners();\n }\n\n /**\n * Set up event listeners\n */\n setupEventListeners() {\n // Listen for collection changes to sync URL\n if (this.urlSyncEnabled && this.collection) {\n // Sync URL when collection params change\n this.collection.on('fetch:start', () => {\n this.isLoading = true;\n });\n\n this.collection.on('fetch:end', () => {\n this.isLoading = false;\n this.lastUpdated = new Date().toLocaleTimeString();\n this.updateStatusDisplay();\n });\n }\n\n // Listen for params-changed event from TableView to sync URL\n this.tableView.on('params-changed', () => {\n if (this.urlSyncEnabled) {\n this.syncUrl();\n }\n });\n\n // // Listen for table events (these also emit params-changed, but keep for backwards compatibility)\n // this.tableView.on('table:search', ({ searchTerm }) => {\n // // params-changed will handle URL sync\n // });\n\n // this.tableView.on('table:sort', ({ field }) => {\n // // params-changed will handle URL sync\n // });\n\n // this.tableView.on('table:page', ({ page }) => {\n // // params-changed will handle URL sync\n // });\n\n // Filter events - params-changed will handle URL sync\n this.tableView.on('filter:edit', async ({ key }) => {\n await this.handleFilterEdit(key);\n });\n\n // Row action events\n this.tableView.on('row:view', async ({ model }) => {\n if (this.onItemView) {\n await this.onItemView(model);\n }\n });\n\n this.tableView.on('row:edit', async ({ model }) => {\n if (this.onItemEdit) {\n await this.onItemEdit(model);\n }\n });\n\n this.tableView.on('row:delete', async ({ model }) => {\n if (this.onItemDelete) {\n await this.onItemDelete(model);\n }\n });\n\n // Table action events\n // Note: TableView will call options.onAdd if provided, but we still listen to the event\n // for backwards compatibility and to support event-based patterns\n this.tableView.on('table:add', async ({ event }) => {\n // The handler was already called by TableView if options.onAdd is set,\n // but we keep this listener for external code that might listen to 'table:add'\n // We don't call this.tableViewConfig.onAdd here to avoid double execution\n });\n\n this.tableView.on('table:export', async ({ data }) => {\n if (this.tableViewConfig.onExport) {\n await this.tableViewConfig.onExport(data);\n }\n });\n }\n\n /**\n * Apply URL query parameters to collection\n */\n applyQueryToCollection() {\n const params = {};\n const query = { ...this.defaultQuery, ...this.query };\n if (!query || Object.keys(query).length === 0) {\n return;\n }\n // Pagination\n if (query.start !== undefined) params.start = parseInt(query.start) || 0;\n if (query.size !== undefined) params.size = parseInt(query.size) || 10;\n\n // Sorting\n if (query.sort !== undefined) params.sort = query.sort;\n\n // Search\n if (query.search !== undefined) params.search = query.search;\n\n // Process all other params as potential filters\n const reservedParams = ['start', 'size', 'sort', 'search', 'page'];\n Object.entries(query).forEach(([key, value]) => {\n if (!reservedParams.includes(key) && value !== undefined && value !== '') {\n // Parse value if it looks like JSON\n if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {\n try {\n params[key] = JSON.parse(value);\n } catch (e) {\n params[key] = value;\n }\n } else {\n params[key] = value;\n }\n }\n });\n\n // Update collection params\n if (Object.keys(params).length > 0) {\n this.collection.setParams({\n ...this.collection.params,\n ...params\n });\n }\n }\n\n /**\n * Sync URL with current table state\n */\n syncUrl(force = true) {\n if (!this.urlSyncEnabled || !this.collection || !this.getApp()?.router) {\n return;\n }\n\n // Get current URL params\n const currentUrl = new URL(window.location);\n const currentParams = {};\n for (const [key, value] of currentUrl.searchParams) {\n if (key !== 'page') {\n currentParams[key] = value;\n }\n }\n\n // Get desired params from collection\n const desiredParams = {};\n const collectionParams = this.collection.params || {};\n\n // Only include non-default values\n if (collectionParams.start) {\n desiredParams.start = collectionParams.start;\n }\n if (collectionParams.size) {\n desiredParams.size = collectionParams.size;\n }\n if (collectionParams.sort) {\n desiredParams.sort = collectionParams.sort;\n }\n if (collectionParams.search) {\n desiredParams.search = collectionParams.search;\n }\n\n // Include other filters\n Object.entries(collectionParams).forEach(([key, value]) => {\n if (!['start', 'size', 'sort', 'search'].includes(key) && value !== undefined && value !== '') {\n // Stringify complex values for URL\n if (typeof value === 'object') {\n desiredParams[key] = JSON.stringify(value);\n } else {\n desiredParams[key] = value;\n }\n }\n });\n\n // Check if there are any changes\n const hasChanges =\n Object.keys(desiredParams).some(key =>\n String(currentParams[key] || '') !== String(desiredParams[key] || '')\n ) ||\n Object.keys(currentParams).some(key =>\n !(key in desiredParams)\n );\n\n this.query = desiredParams;\n if (!hasChanges && !force) return;\n\n // Update URL\n this.updateBrowserUrl(desiredParams, true, false);\n }\n\n /**\n * Update status display\n */\n updateStatusDisplay() {\n if (!this.element) return;\n\n // Update last updated time\n const updatedElement = this.element.querySelector('[data-status=\"last-updated\"]');\n if (updatedElement) {\n updatedElement.textContent = this.lastUpdated || 'Never';\n }\n\n // Update record count\n const countElement = this.element.querySelector('[data-status=\"record-count\"]');\n if (countElement && this.collection) {\n const count = this.collection.meta?.count || this.collection.length();\n countElement.textContent = count;\n }\n }\n\n /**\n * Called when entering this page\n */\n async onEnter() {\n await super.onEnter();\n\n if (this.options.requiresGroup && !this.query[this.groupField] && this.getApp().activeGroup) {\n this.query[this.groupField] = this.getApp().activeGroup.id;\n }\n\n this.applyQueryToCollection();\n\n // Ensure filter pills are shown if there are active filters from URL\n if (this.tableView && this.tableView.element) {\n setTimeout(() => {\n this.tableView.updateFilterPills();\n this.tableView.updateSortIcons();\n }, 100);\n }\n }\n\n /**\n * Public method to refresh the table\n */\n async refresh() {\n await this.tableView.refresh();\n }\n\n /**\n * Public method to get selected items\n */\n getSelectedItems() {\n return this.tableView.getSelectedItems();\n }\n\n /**\n * Public method to clear selection\n */\n clearSelection() {\n this.tableView.clearSelection();\n }\n\n /**\n * Handle filter edit dialog\n */\n async handleFilterEdit(filterKey) {\n // Using statically imported Dialog\n const filterConfig = this.tableView.getAllAvailableFilters().find(f => f.key === filterKey);\n const currentValue = this.collection.params[filterKey];\n\n if (!filterConfig) return;\n\n // Build form field for the filter\n const field = {\n name: 'filter_value',\n label: filterConfig.label || filterKey,\n value: currentValue,\n ...filterConfig.config\n };\n\n const result = await Dialog.showForm({\n title: `Edit ${field.label} Filter`,\n size: 'md',\n fields: [field]\n });\n\n if (result && result.filter_value !== undefined) {\n this.tableView.setFilter(filterKey, result.filter_value);\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n await this.tableView.render();\n this.syncUrl();\n }\n }\n\n /**\n * Clear all filters\n */\n clearAllFilters() {\n if (!this.collection) return;\n\n // Keep only pagination and sort params\n const { start, size, sort } = this.collection.params;\n this.collection.params = { start, size };\n if (sort) this.collection.params.sort = sort;\n\n this.syncUrl();\n\n if (this.collection.restEnabled) {\n this.collection.fetch();\n } else {\n this.tableView.render();\n }\n }\n\n async onGroupChange(group) {\n if (!group || !this.collection || !this.options.requiresGroup) return;\n this.query[this.groupField] = group.id;\n this.applyQueryToCollection();\n if (this.collection && this.collection.restEnabled) {\n await this.collection.fetch();\n }\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n // Remove event listeners\n if (this.collection) {\n this.collection.off('fetch:start');\n this.collection.off('fetch:end');\n }\n\n if (this.tableView) {\n this.tableView.off('params-changed');\n this.tableView.off('table:search');\n this.tableView.off('table:sort');\n this.tableView.off('table:page');\n this.tableView.off('filter:edit');\n this.tableView.off('row:view');\n this.tableView.off('row:edit');\n this.tableView.off('row:delete');\n this.tableView.off('table:add');\n this.tableView.off('table:export');\n }\n\n await super.onBeforeDestroy();\n }\n\n /**\n * Show/hide status display\n */\n get showStatus() {\n return this.options.showStatus === true;\n }\n\n /**\n * Static factory method\n */\n static create(options = {}) {\n return new this(options);\n }\n}\n\nexport default TablePage;\n","/**\n * TabView - Simple tabbed interface component for MOJO framework\n * Displays multiple views in a tabbed interface with clean navigation\n *\n * Features:\n * - Simple tab-based navigation\n * - Child view management with proper mounting/unmounting\n * - Bootstrap 5 styling\n * - Keyboard navigation support\n * - Event-driven tab switching\n *\n * Example Usage:\n * ```javascript\n * const tabView = new TabView({\n * tabs: {\n * 'Profile': new UserProfileView({ data: userData }),\n * 'Settings': new UserSettingsView({ data: settings }),\n * 'Activity': new UserActivityView({ data: activity })\n * },\n * activeTab: 'Profile' // Optional: set initial active tab\n * });\n * ```\n */\n\nimport View from '@core/View.js';\n\nclass TabView extends View {\n constructor(options = {}) {\n const {\n tabs,\n activeTab,\n tabsClass,\n contentClass,\n minWidth,\n enableResponsive,\n tabPadding,\n dropdownStyle, // 'button' (default) or 'select'\n ...viewOptions\n } = options;\n\n super({\n tagName: 'div',\n className: 'tab-view',\n ...viewOptions\n });\n\n // Tab configuration\n this.tabs = {};\n this.tabLabels = Object.keys(this.tabs);\n this.activeTab = activeTab || this.tabLabels[0] || null;\n\n // CSS classes\n this.tabsClass = tabsClass || 'nav nav-tabs mb-3';\n this.contentClass = contentClass || 'tab-content';\n\n // Responsive configuration\n this.dropdownStyle = dropdownStyle || 'select'; // 'button' or 'select'\n this.minWidth = minWidth || 300; // Minimum width before switching to dropdown\n this.enableResponsive = enableResponsive !== false; // Default to enabled\n this.tabPadding = tabPadding || 80; // Estimated padding per tab (16px * 2)\n this.currentMode = 'tabs'; // 'tabs' or 'dropdown'\n\n // Width calculation cache\n this.tabWidthCache = new Map();\n this.lastContainerWidth = 0;\n this.resizeObserver = null;\n this._measurementSpan = null; // Reusable measurement element\n this._tabComputedStyle = null; // To store computed styles\n\n\n\n // State tracking\n this.isMobileMode = false;\n this.hasOverflow = false;\n\n // Initialize tabs by adding them as child views\n for (const [label, view] of Object.entries(tabs)) {\n this.addTab(label, view);\n }\n\n // Bind resize handler\n this.handleResize = this.handleResize.bind(this);\n }\n\n /**\n * Render the tab navigation and content areas\n */\n async renderTemplate() {\n const tabNavigation = this.buildTabNavigation();\n const tabContent = this.buildTabContent();\n\n return `\n <div class=\"tab-view-container\">\n ${tabNavigation}\n ${tabContent}\n </div>\n `;\n }\n\n /**\n * Build the tab navigation HTML - supports both tab and dropdown modes\n * @returns {string} Tab navigation HTML\n */\n buildTabNavigation() {\n if (this.tabLabels.length === 0) {\n return '';\n }\n\n if (this.currentMode === 'dropdown') {\n return this.buildDropdownNavigation();\n } else {\n return this.buildTabsNavigation();\n }\n }\n\n /**\n * Build traditional tab navigation\n * @returns {string} Tab navigation HTML\n */\n buildTabsNavigation() {\n const tabItems = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n const tabId = this.getTabId(label);\n\n return `\n <li class=\"nav-item\" role=\"presentation\">\n <button class=\"nav-link ${isActive ? 'active' : ''}\"\n id=\"${tabId}-tab\"\n data-action=\"show-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"${tabId}\"\n aria-selected=\"${isActive}\">\n ${this.escapeHtml(label)}\n </button>\n </li>\n `;\n }).join('');\n\n return `\n <ul class=\"${this.tabsClass}\" role=\"tablist\" data-tab-mode=\"tabs\">\n ${tabItems}\n </ul>\n `;\n }\n\n /**\n * Build dropdown navigation for mobile/narrow screens\n * @returns {string} Dropdown navigation HTML\n */\n buildDropdownNavigation() {\n const activeLabel = this.activeTab || this.tabLabels[0];\n const dropdownItems = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n return `\n <li>\n <button class=\"dropdown-item ${isActive ? 'active' : ''}\"\n data-action=\"show-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\"\n type=\"button\">\n ${this.escapeHtml(label)}\n ${isActive ? '<i class=\"bi bi-check-lg ms-2\"></i>' : ''}\n </button>\n </li>\n `;\n }).join('');\n\n let buttonHtml;\n if (this.dropdownStyle === 'select') {\n // New \"select\" style button\n buttonHtml = `\n <button class=\"btn tab-view-select-style dropdown-toggle\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n id=\"tab-dropdown-${this.id}\">\n <span class=\"tab-view-select-label\">${this.escapeHtml(activeLabel)}</span>\n </button>\n `;\n } else {\n // Original \"button\" style\n buttonHtml = `\n <button class=\"btn btn-outline-secondary dropdown-toggle w-100 w-sm-auto\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n id=\"tab-dropdown-${this.id}\">\n <i class=\"bi bi-list me-2\"></i>\n ${this.escapeHtml(activeLabel)}\n </button>\n `;\n }\n\n return `\n <div class=\"dropdown mb-3\" data-tab-mode=\"dropdown\">\n ${buttonHtml}\n <ul class=\"dropdown-menu\" aria-labelledby=\"tab-dropdown-${this.id}\">\n ${dropdownItems}\n </ul>\n </div>\n `;\n }\n\n /**\n * Build mobile dropdown navigation\n * @returns {string} Mobile dropdown HTML\n */\n buildMobileDropdownNavigation() {\n const activeLabel = this.activeTab || this.tabLabels[0];\n const dropdownItems = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n return `\n <li>\n <button class=\"dropdown-item ${isActive ? 'active' : ''}\"\n data-action=\"show-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\"\n type=\"button\">\n ${this.escapeHtml(label)}\n ${isActive ? '<i class=\"bi bi-check ms-2\"></i>' : ''}\n </button>\n </li>\n `;\n }).join('');\n\n return `\n <div class=\"dropdown mb-3\" data-tab-navigation=\"mobile\">\n <button class=\"btn btn-outline-secondary dropdown-toggle w-100 text-start\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\">\n <i class=\"bi bi-list me-2\"></i>\n ${this.escapeHtml(activeLabel)}\n </button>\n <ul class=\"dropdown-menu w-100\">\n ${dropdownItems}\n </ul>\n </div>\n `;\n }\n\n /**\n * Determine if mobile dropdown should be used\n * @returns {boolean} True if mobile dropdown should be used\n */\n shouldUseMobileDropdown() {\n if (!this.enableMobileDropdown) return false;\n\n // Check viewport width\n const viewportWidth = window.innerWidth;\n if (viewportWidth < this.mobileBreakpoint) {\n return true;\n }\n\n // Check for overflow in desktop mode\n return this.hasOverflow && viewportWidth < 992; // Bootstrap lg breakpoint\n }\n\n /**\n * Build the tab content area HTML\n * @returns {string} Tab content HTML\n */\n buildTabContent() {\n if (this.tabLabels.length === 0) {\n return '<div class=\"alert alert-info\">No tabs to display</div>';\n }\n\n const tabPanes = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n const tabId = this.getTabId(label);\n\n return `\n <div class=\"tab-pane fade ${isActive ? 'show active' : ''}\"\n id=\"${tabId}\"\n role=\"tabpanel\"\n aria-labelledby=\"${tabId}-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\">\n <div data-container=\"${tabId}-content\"></div>\n </div>\n `;\n }).join('');\n\n return `\n <div class=\"${this.contentClass}\">\n ${tabPanes}\n </div>\n `;\n }\n\n /**\n * Generate a safe DOM ID for a tab\n * @param {string} label - Tab label\n * @returns {string} Safe DOM ID\n */\n getTabId(label) {\n return `tab-${label.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${this.id}`;\n }\n\n /**\n * Show a specific tab\n * @param {string} tabLabel - Label of the tab to show\n * @returns {Promise<boolean>} True if tab was shown successfully\n */\n async showTab(tabLabel, options = {}) {\n const { force = false } = options;\n\n // Validate tab exists\n if (!this.tabs[tabLabel]) {\n console.warn(`TabView: Tab \"${tabLabel}\" does not exist`);\n return false;\n }\n\n // Skip if already active and not being forced\n // This prevents re-rendering the same tab on repeated clicks, but allows\n // for forced re-mounting after a parent view render.\n if (this.activeTab === tabLabel && !force) {\n // As a safeguard, ensure the view is actually in the DOM.\n // If not, proceed to re-mount it.\n const activeView = this.tabs[tabLabel];\n if (activeView && activeView.isMounted() && this.element.contains(activeView.element)) {\n return true;\n }\n }\n\n const previousTab = this.activeTab;\n this.activeTab = tabLabel;\n\n try {\n // Update tab navigation\n await this.updateTabNavigation(tabLabel, previousTab);\n\n // Update tab content\n await this.updateTabContent(tabLabel, previousTab);\n\n // Emit tab change event\n this.emit('tab:changed', {\n activeTab: tabLabel,\n previousTab: previousTab\n });\n\n return true;\n } catch (error) {\n console.error('TabView: Error showing tab:', error);\n this.activeTab = previousTab; // Revert on error\n return false;\n }\n }\n\n /**\n * Update tab navigation visual state\n * @param {string} activeTabLabel - New active tab\n * @param {string} previousTabLabel - Previous active tab\n */\n async updateTabNavigation(activeTabLabel, previousTabLabel) {\n if (!this.element) return;\n\n // In dropdown mode, re-render the navigation to update the button label and items\n if (this.currentMode === 'dropdown') {\n await this.reRenderNavigation();\n return; // Re-rendering handles all visual updates for navigation\n }\n\n // In tabs mode, just toggle classes for efficiency\n if (previousTabLabel) {\n const prevTabButton = this.element.querySelector(`[data-tab-label=\"${previousTabLabel}\"]`);\n if (prevTabButton) {\n prevTabButton.classList.remove('active');\n prevTabButton.setAttribute('aria-selected', 'false');\n }\n }\n\n // Add active state to new tab\n const activeTabButton = this.element.querySelector(`[data-tab-label=\"${activeTabLabel}\"]`);\n if (activeTabButton) {\n activeTabButton.classList.add('active');\n activeTabButton.setAttribute('aria-selected', 'true');\n }\n }\n\n /**\n * Update tab content area and manage child views\n * @param {string} activeTabLabel - New active tab\n * @param {string} previousTabLabel - Previous active tab\n */\n async updateTabContent(activeTabLabel, previousTabLabel) {\n if (!this.element) return;\n\n const activeTabId = this.getTabId(activeTabLabel);\n const previousTabId = previousTabLabel ? this.getTabId(previousTabLabel) : null;\n\n // Hide previous tab pane\n if (previousTabId) {\n const prevPane = this.element.querySelector(`#${previousTabId}`);\n if (prevPane) {\n prevPane.classList.remove('show', 'active');\n }\n }\n\n // Show active tab pane\n const activePane = this.element.querySelector(`#${activeTabId}`);\n if (activePane) {\n activePane.classList.add('show', 'active');\n }\n\n // Mount the active tab's view\n const activeView = this.tabs[activeTabLabel];\n if (activeView) {\n const container = this.element.querySelector(`[data-container=\"${activeTabId}-content\"]`);\n if (container && !activeView.isMounted()) {\n await activeView.render(true, container);\n }\n if (activeView.onTabActivated) {\n await activeView.onTabActivated();\n }\n }\n }\n\n /**\n * Handle tab click events\n * @param {Event} event - Click event\n * @param {HTMLElement} element - Clicked element\n */\n async onActionShowTab(event, element) {\n const tabLabel = element.getAttribute('data-tab-label');\n if (tabLabel) {\n await this.showTab(tabLabel);\n }\n }\n\n /**\n * Initialize measurement styles by reading computed styles from a rendered tab\n */\n _initializeMeasurementStyles() {\n if (!this.element || this._tabComputedStyle) return;\n\n const tabButton = this.element.querySelector('.nav-link');\n if (tabButton && typeof window.getComputedStyle === 'function') {\n const style = window.getComputedStyle(tabButton);\n this._tabComputedStyle = {\n font: style.font,\n letterSpacing: style.letterSpacing,\n };\n\n // Calculate horizontal padding from computed styles\n const paddingLeft = parseFloat(style.paddingLeft) || 0;\n const paddingRight = parseFloat(style.paddingRight) || 0;\n this.tabPadding = paddingLeft + paddingRight + 12; // Update with real value\n }\n }\n\n /**\n * Initialize active tab after rendering\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Initialize styles for accurate width calculation before setting up responsive handling\n this._initializeMeasurementStyles();\n\n // Set up responsive behavior\n if (this.enableResponsive) {\n this.setupResponsiveHandling();\n }\n\n // Show the active tab after initial render. Forcing ensures the content is\n // correctly re-mounted if the TabView itself was re-rendered.\n if (this.activeTab && this.tabs[this.activeTab]) {\n await this.showTab(this.activeTab, { force: true });\n }\n }\n\n /**\n * Mount child views after tab view is mounted\n */\n async onAfterMount() {\n await super.onAfterMount();\n\n // The active tab's content is now mounted via the onAfterRender -> showTab flow,\n // which correctly handles both initial renders and subsequent re-renders.\n // The logic previously here was redundant.\n }\n\n /**\n * Clean up child views when destroying\n */\n async onBeforeDestroy() {\n await super.onBeforeDestroy();\n\n // Clean up resize observer\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n\n // Remove window resize listener\n if (typeof window !== 'undefined') {\n window.removeEventListener('resize', this.handleResize);\n }\n\n // Remove measurement span if it exists\n if (this._measurementSpan && this._measurementSpan.parentElement) {\n this._measurementSpan.parentElement.removeChild(this._measurementSpan);\n }\n this._measurementSpan = null;\n\n // Destroy all child tab views\n for (const [label, view] of Object.entries(this.tabs)) {\n if (view && typeof view.destroy === 'function') {\n await view.destroy();\n }\n }\n }\n\n /**\n * Get the currently active tab label\n * @returns {string|null} Active tab label\n */\n getActiveTab() {\n return this.activeTab;\n }\n\n /**\n * Get all tab labels\n * @returns {string[]} Array of tab labels\n */\n getTabLabels() {\n return [...this.tabLabels];\n }\n\n /**\n * Get a specific tab's view by label\n * @param {string} label - Tab label\n * @returns {View|null} The tab's view instance or null if not found\n */\n getTab(label) {\n return this.tabs[label] || null;\n }\n\n /**\n * Add a new tab\n * @param {string} label - Tab label\n * @param {View} view - View instance for tab content\n * @param {boolean} makeActive - Whether to make this tab active\n * @returns {Promise<boolean>} True if tab was added successfully\n */\n async addTab(label, view, makeActive = false) {\n if (this.tabs[label]) {\n console.warn(`TabView: Tab \"${label}\" already exists`);\n return false;\n }\n\n if (view.options.permissions && !this.getApp().activeUser.hasPerm(view.options.permissions)) {\n return false;\n }\n\n this.tabs[label] = view;\n // this.addChild(view);\n view.containerId = this.getTabId(label);\n view.parent = this;\n this.tabLabels = Object.keys(this.tabs);\n\n // Set as active tab if it's the first tab or explicitly requested\n if (!this.activeTab || makeActive) {\n this.activeTab = label;\n }\n\n // Re-render if mounted\n if (this.isMounted()) {\n await this.render();\n\n if (makeActive || this.activeTab === label) {\n await this.showTab(label);\n }\n }\n\n this.emit('tab:added', { label, view });\n return true;\n }\n\n /**\n * Remove a tab\n * @param {string} label - Tab label to remove\n * @returns {Promise<boolean>} True if tab was removed successfully\n */\n async removeTab(label) {\n if (!this.tabs[label]) {\n console.warn(`TabView: Tab \"${label}\" does not exist`);\n return false;\n }\n\n const view = this.tabs[label];\n\n // Destroy the view\n if (view && typeof view.destroy === 'function') {\n await view.destroy();\n }\n\n // Remove from tabs\n delete this.tabs[label];\n this.tabLabels = Object.keys(this.tabs);\n\n // Handle active tab removal\n if (this.activeTab === label) {\n this.activeTab = this.tabLabels[0] || null;\n }\n\n // Re-render if mounted\n if (this.isMounted()) {\n await this.render();\n\n if (this.activeTab) {\n await this.showTab(this.activeTab);\n }\n }\n\n this.emit('tab:removed', { label, view });\n return true;\n }\n\n /**\n * Calculate estimated width needed for a tab label\n * @param {string} label - Tab label text\n * @returns {number} Estimated width in pixels\n */\n calculateTabWidth(label) {\n if (this.tabWidthCache.has(label)) {\n return this.tabWidthCache.get(label);\n }\n\n // Fallback for non-browser environments\n if (typeof document === 'undefined') {\n const estimatedWidth = label.length * 8 + this.tabPadding; // Original fallback\n this.tabWidthCache.set(label, estimatedWidth);\n return estimatedWidth;\n }\n\n // Create a reusable measurement span if it doesn't exist\n if (!this._measurementSpan) {\n this._measurementSpan = document.createElement('span');\n this._measurementSpan.style.visibility = 'hidden';\n this._measurementSpan.style.position = 'absolute';\n this._measurementSpan.style.whiteSpace = 'nowrap';\n }\n\n const span = this._measurementSpan;\n\n // Apply computed styles if available, otherwise use defaults\n if (this._tabComputedStyle) {\n span.style.font = this._tabComputedStyle.font;\n span.style.letterSpacing = this._tabComputedStyle.letterSpacing;\n } else {\n // Fallback to original hardcoded styles if computed style not yet available\n span.style.fontSize = '14px';\n span.style.fontFamily = 'system-ui, -apple-system, sans-serif';\n }\n\n span.textContent = label;\n document.body.appendChild(span);\n const width = span.offsetWidth + this.tabPadding;\n document.body.removeChild(span);\n\n this.tabWidthCache.set(label, width);\n return width;\n }\n\n /**\n * Calculate total width needed for all tabs\n * @returns {number} Total width in pixels\n */\n getTotalTabWidth() {\n return this.tabLabels.reduce((total, label) => {\n return total + this.calculateTabWidth(label);\n }, 0);\n }\n\n /**\n * Get current container width\n * @returns {number} Container width in pixels\n */\n getContainerWidth() {\n if (!this.element) {\n return this.minWidth;\n }\n\n const container = this.element.parentElement || this.element;\n return container.offsetWidth || this.minWidth;\n }\n\n /**\n * Determine if dropdown mode should be used\n * @returns {boolean} True if dropdown mode should be used\n */\n shouldUseDropdown() {\n if (!this.enableResponsive) {\n return false;\n }\n\n const containerWidth = this.getContainerWidth();\n const totalTabWidth = this.getTotalTabWidth();\n\n // Use dropdown if tabs would overflow or container is too narrow\n return containerWidth < Math.max(totalTabWidth, this.minWidth);\n }\n\n /**\n * Setup responsive handling with ResizeObserver\n */\n setupResponsiveHandling() {\n if (!this.element || !this.enableResponsive) {\n return;\n }\n\n // Set initial mode\n this.updateNavigationMode();\n\n // Use ResizeObserver for better performance\n if (typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => {\n this.handleResize();\n });\n\n const container = this.element.parentElement || this.element;\n this.resizeObserver.observe(container);\n } else {\n // Fallback to window resize\n window.addEventListener('resize', this.handleResize);\n }\n }\n\n /**\n * Handle resize events\n */\n async handleResize() {\n const containerWidth = this.getContainerWidth();\n\n // Only update if width changed significantly (avoid excessive updates)\n if (Math.abs(containerWidth - this.lastContainerWidth) > 50) {\n this.lastContainerWidth = containerWidth;\n await this.updateNavigationMode();\n }\n }\n\n /**\n * Update navigation mode based on current space\n */\n async updateNavigationMode() {\n const shouldUseDropdown = this.shouldUseDropdown();\n const newMode = shouldUseDropdown ? 'dropdown' : 'tabs';\n\n if (newMode !== this.currentMode) {\n this.currentMode = newMode;\n\n // Re-render navigation if mounted\n if (this.isMounted()) {\n await this.reRenderNavigation();\n }\n\n // Emit mode change event\n this.emit('navigation:modeChanged', {\n mode: this.currentMode,\n containerWidth: this.getContainerWidth(),\n totalTabWidth: this.getTotalTabWidth()\n });\n }\n }\n\n /**\n * Re-render just the navigation part\n */\n async reRenderNavigation() {\n if (!this.element) return;\n\n const navigationContainer = this.element.querySelector('[data-tab-mode]');\n if (navigationContainer) {\n const newNavigation = this.buildTabNavigation();\n navigationContainer.outerHTML = newNavigation;\n }\n }\n\n /**\n * Get current navigation mode\n * @returns {string} Current mode ('tabs' or 'dropdown')\n */\n getNavigationMode() {\n return this.currentMode;\n }\n\n /**\n * Force a specific navigation mode\n * @param {string} mode - 'tabs' or 'dropdown'\n */\n async setNavigationMode(mode) {\n if (mode !== 'tabs' && mode !== 'dropdown') {\n console.warn('TabView: Invalid navigation mode. Use \"tabs\" or \"dropdown\"');\n return;\n }\n\n this.currentMode = mode;\n\n if (this.isMounted()) {\n await this.reRenderNavigation();\n }\n }\n\n /**\n * Clear the tab width cache (useful when tab labels change)\n */\n clearWidthCache() {\n this.tabWidthCache.clear();\n }\n\n /**\n * Static factory method\n * @param {object} options - TabView options\n * @returns {TabView} New TabView instance\n */\n static create(options = {}) {\n return new TabView(options);\n }\n}\n\nexport default TabView;\n","import View from '@core/View.js';\n\nclass FilePreviewView extends View {\n constructor(options = {}) {\n super({\n className: 'file-preview',\n ...options\n });\n this.file = options.file || {};\n this.isImage = this.file.content_type?.startsWith('image/');\n this.isPdf = this.file.content_type === 'application/pdf';\n }\n\n getTemplate() {\n return `\n <div class=\"file-preview-item card card-body p-2 mt-2\">\n <div class=\"d-flex align-items-center\">\n <div class=\"flex-shrink-0\">\n ${this.isImage ? `<img src=\"${this.file.thumbnailUrl || this.file.url}\" class=\"rounded\" style=\"width: 40px; height: 40px; object-fit: cover;\">` : `<i class=\"bi bi-file-earmark-text fs-2 text-secondary\"></i>`}\n </div>\n <div class=\"flex-grow-1 ms-3\">\n <div class=\"fw-bold text-truncate\">{{file.filename}}</div>\n <div class=\"small text-muted\">{{file.file_size|filesize}}</div>\n </div>\n <div class=\"flex-shrink-0\">\n <button class=\"btn btn-sm btn-outline-primary\" data-action=\"view-file\">View</button>\n </div>\n </div>\n </div>\n `;\n }\n\n async onActionViewFile() {\n if (this.isImage) {\n // Check if lightbox extension is available\n const LightboxGallery = window.MOJO?.plugins?.LightboxGallery;\n \n if (LightboxGallery) {\n LightboxGallery.show({ src: this.file.url, alt: this.file.filename });\n } else {\n // Fallback: open in new tab\n window.open(this.file.url, '_blank');\n }\n } else if (this.isPdf) {\n // Check if lightbox extension is available\n const PDFViewer = window.MOJO?.plugins?.PDFViewer;\n \n if (PDFViewer) {\n PDFViewer.showDialog(this.file.url, { title: this.file.filename });\n } else {\n // Fallback: open in new tab\n window.open(this.file.url, '_blank');\n }\n } else {\n window.open(this.file.url, '_blank');\n }\n }\n}\n\nexport default FilePreviewView;\n","import View from '@core/View.js';\nimport FilePreviewView from '@core/views/data/FilePreviewView.js';\n\n/**\n * ChatMessageView - Individual message display with theme support\n * \n * Supports two themes:\n * - 'compact': List-based admin/activity feed style\n * - 'bubbles': Chat bubble style with left/right positioning\n */\nclass ChatMessageView extends View {\n constructor(options = {}) {\n super({\n className: 'chat-message',\n ...options\n });\n \n this.message = options.message || {};\n this.theme = options.theme || 'compact';\n this.isCurrentUser = options.isCurrentUser || false;\n \n // Add theme-specific class\n if (this.theme === 'bubbles') {\n this.className += this.isCurrentUser ? ' message-right' : ' message-left';\n }\n }\n\n getTemplate() {\n // System event messages (same for both themes)\n if (this.message.type === 'system_event') {\n return `\n <div class=\"chat-message-system text-center text-muted small py-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n {{message.content}} on {{message.timestamp|datetime}}\n </div>\n `;\n }\n\n // Theme-specific templates\n if (this.theme === 'bubbles') {\n return this.getBubblesTemplate();\n } else {\n return this.getCompactTemplate();\n }\n }\n\n /**\n * Get compact theme template (Option 4 - Admin/Activity Feed Style)\n */\n getCompactTemplate() {\n const userClass = this.isCurrentUser ? 'bg-primary' : 'bg-secondary';\n \n return `\n <div class=\"message-item\">\n <div class=\"message-avatar ${userClass}\">\n {{#message.author.avatarUrl}}\n <img src=\"{{message.author.avatarUrl}}\" alt=\"{{message.author.name}}\" class=\"w-100 h-100 rounded-circle\">\n {{/message.author.avatarUrl}}\n {{^message.author.avatarUrl}}\n {{message.author.name|initials}}\n {{/message.author.avatarUrl}}\n </div>\n <div class=\"message-content\">\n <div class=\"message-header\">\n <div class=\"message-author\">\n {{message.author.name}}\n {{#isCurrentUser}}\n <span class=\"badge bg-primary badge-sm ms-1\">You</span>\n {{/isCurrentUser}}\n </div>\n <div class=\"message-time text-muted\">{{message.timestamp|relative}}</div>\n </div>\n <div class=\"message-text\">{{{message.content}}}</div>\n <div data-container=\"attachments\"></div>\n </div>\n </div>\n `;\n }\n\n /**\n * Get bubbles theme template (Option 1 - Modern Chat Bubbles)\n */\n getBubblesTemplate() {\n return `\n <div class=\"message-bubble-wrapper\">\n <div class=\"message-meta\">\n <strong>{{message.author.name}}</strong>\n <span class=\"text-muted\">· {{message.timestamp|relative}}</span>\n </div>\n <div class=\"message-bubble\">\n <div class=\"message-text\">{{{message.content}}}</div>\n <div data-container=\"attachments\"></div>\n </div>\n </div>\n `;\n }\n\n async onAfterRender() {\n // Render attachments if any\n if (this.message.attachments && this.message.attachments.length > 0) {\n const attachmentsContainer = this.element.querySelector('[data-container=\"attachments\"]');\n if (attachmentsContainer) {\n this.message.attachments.forEach(file => {\n const filePreview = new FilePreviewView({ file });\n this.addChild(filePreview);\n filePreview.render(true, attachmentsContainer);\n });\n }\n }\n }\n}\n\nexport default ChatMessageView;\n","import View from '@core/View.js';\nimport applyFileDropMixin from '@core/mixins/FileDropMixin.js';\nimport { File } from '@core/models/Files.js';\n\n/**\n * ChatInputView - Input area with file drop support and attachment preview\n */\nclass ChatInputView extends View {\n constructor(options = {}) {\n super({\n className: 'chat-input-view',\n ...options\n });\n\n this.placeholder = options.placeholder || 'Type a message...';\n this.buttonText = options.buttonText || 'Send';\n this.attachments = []; // Array of uploaded file data\n this.pendingUploads = new Map(); // Track in-progress uploads\n }\n\n getTemplate() {\n return `\n <div class=\"chat-input-container\">\n <div class=\"chat-input-attachments\" data-container=\"attachments\"></div>\n <div class=\"chat-input-wrapper\">\n <textarea\n class=\"chat-input form-control\"\n placeholder=\"${this.placeholder}\"\n rows=\"2\"></textarea>\n <button class=\"chat-send-btn btn btn-primary\" data-action=\"send-message\" type=\"button\">\n <i class=\"bi bi-send-fill\"></i>\n <span class=\"spinner-border spinner-border-sm d-none\" role=\"status\" aria-hidden=\"true\"></span>\n </button>\n </div>\n <div class=\"chat-input-footer\">\n <small class=\"text-muted\">\n <i class=\"bi bi-paperclip\"></i>\n Drag & drop files to attach\n </small>\n </div>\n </div>\n `;\n }\n\n async onAfterRender() {\n // Enable file drop on the entire input container\n this.enableFileDrop({\n dropZoneSelector: '.chat-input-container',\n multiple: true,\n acceptedTypes: ['*/*'], // Accept all file types\n visualFeedback: true,\n dragOverClass: 'drag-over',\n dragActiveClass: 'drag-active'\n });\n\n // Auto-resize textarea as user types and handle Enter key\n const textarea = this.element.querySelector('.chat-input');\n if (textarea) {\n textarea.addEventListener('input', () => this.autoResizeTextarea(textarea));\n textarea.addEventListener('keydown', (e) => this.handleKeydown(e));\n }\n }\n\n /**\n * Handle textarea keydown (send on Enter without Shift)\n */\n handleKeydown(event) {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n this.onActionSendMessage(event, event.target);\n }\n }\n\n /**\n * Handle file drop\n * @param {File[]} files - Dropped files\n */\n async onFileDrop(files) {\n for (const file of files) {\n await this.uploadFile(file);\n }\n }\n\n /**\n * Upload a file\n * @param {File} file - File to upload\n */\n async uploadFile(file) {\n const fileModel = new File();\n const uploadId = Date.now() + Math.random();\n\n // Add preview immediately\n this.addFilePreview(uploadId, file, 0);\n this.pendingUploads.set(uploadId, { file, fileModel });\n\n try {\n const result = await fileModel.upload({\n file: file,\n onProgress: (progress) => {\n this.updateFileProgress(uploadId, progress);\n },\n onComplete: (uploadResult) => {\n this.handleUploadComplete(uploadId, fileModel);\n }\n });\n\n } catch (error) {\n console.error('File upload failed:', error);\n this.handleUploadError(uploadId, error);\n }\n }\n\n /**\n * Add file preview to UI\n * @param {string} uploadId - Unique upload ID\n * @param {File} file - File object\n * @param {number} progress - Upload progress (0-100)\n */\n addFilePreview(uploadId, file, progress) {\n const container = this.element.querySelector('[data-container=\"attachments\"]');\n if (!container) return;\n\n const preview = document.createElement('div');\n preview.className = 'attachment-preview';\n preview.dataset.uploadId = uploadId;\n preview.innerHTML = `\n <div class=\"attachment-info\">\n <i class=\"bi bi-file-earmark\"></i>\n <span class=\"attachment-name\">${this.escapeHtml(file.name)}</span>\n <span class=\"attachment-size\">(${this.formatFileSize(file.size)})</span>\n </div>\n <div class=\"attachment-progress\">\n <div class=\"progress\" style=\"height: 4px;\">\n <div class=\"progress-bar\" role=\"progressbar\" style=\"width: ${progress}%\"></div>\n </div>\n </div>\n <button class=\"attachment-remove btn btn-sm btn-link text-danger\" data-action=\"remove-attachment\" data-upload-id=\"${uploadId}\" type=\"button\">\n <i class=\"bi bi-x\"></i>\n </button>\n `;\n\n container.appendChild(preview);\n }\n\n /**\n * Update file upload progress\n * @param {string} uploadId - Upload ID\n * @param {number} progress - Progress (0-100)\n */\n updateFileProgress(uploadId, progress) {\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n const progressBar = preview.querySelector('.progress-bar');\n if (progressBar) {\n progressBar.style.width = `${progress}%`;\n }\n }\n }\n\n /**\n * Handle upload completion\n * @param {string} uploadId - Upload ID\n * @param {Object} result - Upload result data (contains file.id)\n */\n handleUploadComplete(uploadId, fileModel) {\n // Store the file data with its ID\n this.attachments.push({\n id: fileModel.id,\n name: fileModel.get(\"name\"),\n uploadId: uploadId\n });\n this.pendingUploads.delete(uploadId);\n\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n preview.classList.add('upload-complete');\n const progressContainer = preview.querySelector('.attachment-progress');\n if (progressContainer) {\n progressContainer.remove();\n }\n }\n }\n\n /**\n * Handle upload error\n * @param {string} uploadId - Upload ID\n * @param {Error} error - Error object\n */\n handleUploadError(uploadId, error) {\n this.pendingUploads.delete(uploadId);\n\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n preview.classList.add('upload-error');\n preview.querySelector('.attachment-info').innerHTML +=\n `<span class=\"text-danger ms-2\">Upload failed</span>`;\n }\n }\n\n /**\n * Remove attachment\n */\n async onActionRemoveAttachment(event, element) {\n const uploadId = element.dataset.uploadId;\n\n // Remove from pending uploads\n this.pendingUploads.delete(uploadId);\n\n // Remove from completed attachments\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n // TODO: Get the file ID from the preview and remove from attachments array\n preview.remove();\n }\n }\n\n\n\n /**\n * Send message\n */\n async onActionSendMessage(event, element) {\n const textarea = this.element.querySelector('.chat-input');\n const text = textarea.value.trim();\n\n // Don't send if empty and no attachments\n if (!text && this.attachments.length === 0) {\n return;\n }\n\n // Don't send if uploads are pending\n if (this.pendingUploads.size > 0) {\n // TODO: Show message that uploads are in progress\n return;\n }\n\n // Show busy state\n this.setBusy(true);\n\n // Emit event with message data\n this.emit('message:send', {\n text: text,\n files: this.attachments\n });\n\n // Note: Don't clear here - let the parent ChatView call clearInput() after successful send\n }\n\n /**\n * Set busy state (show/hide spinner)\n * @param {boolean} busy - Whether to show busy state\n */\n setBusy(busy) {\n const button = this.element.querySelector('.chat-send-btn');\n const icon = button.querySelector('.bi-send-fill');\n const spinner = button.querySelector('.spinner-border');\n\n if (busy) {\n button.disabled = true;\n icon.classList.add('d-none');\n spinner.classList.remove('d-none');\n } else {\n button.disabled = false;\n icon.classList.remove('d-none');\n spinner.classList.add('d-none');\n }\n }\n\n /**\n * Clear input and attachments\n */\n clearInput() {\n const textarea = this.element.querySelector('.chat-input');\n if (textarea) {\n textarea.value = '';\n textarea.style.height = 'auto';\n }\n\n const container = this.element.querySelector('[data-container=\"attachments\"]');\n if (container) {\n container.innerHTML = '';\n }\n\n this.attachments = [];\n this.pendingUploads.clear();\n \n // Reset busy state\n this.setBusy(false);\n }\n\n /**\n * Auto-resize textarea based on content\n * @param {HTMLTextAreaElement} textarea\n */\n autoResizeTextarea(textarea) {\n textarea.style.height = 'auto';\n textarea.style.height = Math.min(textarea.scrollHeight, 150) + 'px';\n }\n\n /**\n * Format file size for display\n * @param {number} bytes\n * @returns {string}\n */\n formatFileSize(bytes) {\n if (bytes === 0) return '0 B';\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];\n }\n\n /**\n * Escape HTML to prevent XSS\n * @param {string} text\n * @returns {string}\n */\n escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n}\n\n// Apply FileDropMixin\napplyFileDropMixin(ChatInputView);\n\nexport default ChatInputView;\n","import View from '@core/View.js';\nimport ChatMessageView from './ChatMessageView.js';\nimport ChatInputView from './ChatInputView.js';\n\n/**\n * ChatView - Modern chat interface with theme support\n * \n * Themes:\n * - 'compact' (default): Admin/activity feed style, list-based layout\n * - 'bubbles': Modern chat bubbles with left/right positioning\n * \n * Usage:\n * const chat = new ChatView({\n * adapter: myAdapter,\n * theme: 'compact', // or 'bubbles'\n * currentUserId: 123,\n * inputPlaceholder: 'Add a comment...',\n * inputButtonText: 'Send'\n * });\n */\nclass ChatView extends View {\n constructor(options = {}) {\n super({\n className: 'chat-view',\n ...options\n });\n \n this.adapter = options.adapter;\n this.theme = options.theme || 'compact'; // 'compact' or 'bubbles'\n this.currentUserId = options.currentUserId;\n this.inputPlaceholder = options.inputPlaceholder || 'Type a message...';\n this.inputButtonText = options.inputButtonText || 'Send';\n this.messages = [];\n this.messageViews = new Map(); // Track message views by ID\n }\n\n getTemplate() {\n return `\n <div class=\"chat-container chat-theme-${this.theme}\">\n <div class=\"chat-messages\" data-container=\"messages\"></div>\n <div class=\"chat-input-wrapper\" data-container=\"input\"></div>\n </div>\n `;\n }\n\n async onInit() {\n // Initial fetch of messages\n this.messages = await this.adapter.fetch();\n \n // Create input view\n this.inputView = new ChatInputView({\n containerId: 'input',\n placeholder: this.inputPlaceholder,\n buttonText: this.inputButtonText\n });\n this.addChild(this.inputView);\n \n // Listen for new messages\n this.inputView.on('message:send', async (data) => {\n await this.handleSendMessage(data);\n });\n }\n\n async onAfterRender() {\n // Build message views\n this._buildMessageViews();\n \n // Render children (like ListView does)\n await this._renderChildren();\n \n // Scroll to bottom\n this.scrollToBottom();\n }\n\n /**\n * Render child message views (similar to ListView._renderChildren)\n * @private\n */\n async _renderChildren() {\n await super._renderChildren();\n const messagesContainer = this.element.querySelector('[data-container=\"messages\"]');\n if (!messagesContainer) {\n console.error('ChatView: messages container not found');\n return;\n }\n \n // Append each message view to the container and render it\n this.messageViews.forEach((messageView) => {\n messagesContainer.appendChild(messageView.element);\n messageView.render(false);\n });\n }\n\n /**\n * Build message views for all messages (similar to ListView._buildItems)\n * @private\n */\n _buildMessageViews() {\n if (!this.messages || this.messages.length === 0) return;\n \n this.messages.forEach(message => {\n if (!this.messageViews.has(message.id)) {\n this._createMessageView(message);\n }\n });\n }\n\n /**\n * Create a message view (similar to ListView._createItemView)\n * @private\n */\n _createMessageView(message) {\n if (this.messageViews.has(message.id)) return;\n \n const isCurrentUser = message.author && message.author.id === this.currentUserId;\n \n const messageView = new ChatMessageView({\n message: message,\n theme: this.theme,\n isCurrentUser: isCurrentUser\n });\n \n this.addChild(messageView);\n this.messageViews.set(message.id, messageView);\n \n return messageView;\n }\n\n /**\n * Add a new message to the chat (for real-time updates)\n * @param {Object} message - Message data\n * @param {boolean} scroll - Whether to scroll to bottom after adding\n */\n addMessage(message, scroll = true) {\n if (this.messageViews.has(message.id)) return;\n \n const messageView = this._createMessageView(message);\n \n // If already rendered, append to DOM immediately\n if (this.isMounted()) {\n const messagesContainer = this.element.querySelector('[data-container=\"messages\"]');\n if (messagesContainer) {\n messagesContainer.appendChild(messageView.element);\n messageView.render(false);\n }\n }\n \n if (scroll) {\n this.scrollToBottom();\n }\n }\n\n /**\n * Handle sending a new message\n * @param {Object} data - Message data {text, files}\n * @private\n */\n async handleSendMessage(data) {\n try {\n // If there's text, send it as a note\n if (data.text && data.text.trim()) {\n const result = await this.adapter.addNote({\n text: data.text,\n files: data.files && data.files.length > 0 ? [data.files[0]] : []\n });\n \n if (!result.success) {\n throw new Error('Failed to send message');\n }\n }\n \n // If there are multiple files, or files without text, create a note for each\n const startIndex = (data.text && data.text.trim() && data.files.length > 0) ? 1 : 0;\n \n for (let i = startIndex; i < (data.files?.length || 0); i++) {\n const file = data.files[i];\n const result = await this.adapter.addNote({\n text: '', // Empty text, just the file\n files: [file]\n });\n \n if (!result.success) {\n console.error('Failed to upload file:', file);\n }\n }\n \n // Fetch updated messages\n this.messages = await this.adapter.fetch();\n \n // Find the new message(s) and add them\n this.messages.forEach(message => {\n if (!this.messageViews.has(message.id)) {\n this.addMessage(message, true);\n }\n });\n \n // Clear input (this also resets busy state)\n this.inputView.clearInput();\n \n } catch (error) {\n console.error('Failed to send message:', error);\n // Reset busy state on error\n this.inputView.setBusy(false);\n // TODO: Show error feedback to user\n }\n }\n\n /**\n * Scroll chat to bottom\n */\n scrollToBottom() {\n const container = this.element.querySelector('.chat-messages');\n if (container) {\n requestAnimationFrame(() => {\n container.scrollTop = container.scrollHeight;\n });\n }\n }\n\n /**\n * Clear all messages\n */\n clearMessages() {\n this.messageViews.forEach(view => view.destroy());\n this.messageViews.clear();\n this.messages = [];\n \n const container = this.element.querySelector('[data-container=\"messages\"]');\n if (container) {\n container.innerHTML = '';\n }\n }\n\n /**\n * Refresh messages from adapter\n */\n async refresh() {\n this.clearMessages();\n this.messages = await this.adapter.fetch();\n this._buildMessageViews();\n \n if (this.isMounted()) {\n await this._renderChildren();\n this.scrollToBottom();\n }\n }\n}\n\nexport default ChatView;\n"],"names":["S3Bucket","Model","constructor","data","super","endpoint","S3BucketList","Collection","options","ModelClass","size","EmailDomain","onboard","this","id","showError","success","status","error","url","buildUrl","rest","POST","params","err","message","audit","method","toUpperCase","GET","reconcile","onboardById","model","auditById","reconcileById","EmailDomainList","Mailbox","sendEmail","async","MailboxList","MailboxForms","create","title","fields","type","name","label","labelField","valueField","maxItems","placeholder","emptyFetch","required","debounceMs","columns","defaultValue","help","edit","SentMessage","SentMessageList","EmailTemplate","EmailTemplateList","ProgressView","View","template","filename","filesize","filesizeFormatted","dataFormatter","pipe","progress","percentage","loaded","total","loadedFormatted","totalFormatted","showCancel","onCancel","cancelled","completed","getTemplate","updateProgress","progressInfo","render","markCompleted","markFailed","markCancelled","onActionCancel","action","event","element","disabled","emit","console","setFilename","setFilesize","getPercentage","isCompleted","isCancelled","getStats","FileUpload","fileModel","file","group","description","onProgress","onComplete","onError","showToast","File","Error","uploadRequest","progressToast","progressView","toastService","ToastService","promise","_startUpload","uploadData","result","_showProgressToast","_initiateUpload","upload_url","_performUpload","_completeUpload","warn","_onComplete","_onError","payload","file_size","content_type","response","errorMessage","set","uploadUrl","Promise","resolve","reject","xhr","XMLHttpRequest","upload","onprogress","Math","round","_onProgress","onload","statusText","onerror","ontimeout","onabort","open","setRequestHeader","timeout","send","save","setTimeout","hide","cancel","showView","autohide","dismissible","abort","then","onSuccess","catch","onFinally","finally","FileManager","FileManagerList","FileManagerForms","cols","value","text","default","owners","GroupList","UserList","credentials","isImage","get","FileList","IncidentStats","requiresId","IncidentEvent","IncidentEventList","IncidentEventForms","Incident","COMPONENTS","editable","force_top","IncidentList","IncidentRuleSet","IncidentRuleSetList","IncidentRule","IncidentRuleList","IncidentHistory","IncidentHistoryList","BundleByOptions","MatchByOptions","ComparatorOptions","ValueTypeOptions","CommonEventFields","meta","RuleSet","RuleSetList","RuleSetForms","Rule","RuleList","RuleForms","allowCustom","showDescription","EDIT_FORM","ADD_FORM","CREATE_FORM","Job","cancel_request","retry","delay","retry_request","new_job_id","newJobId","originalJobId","original_job_id","getDetailedStatus","get_status","cloneJob","newPayload","publishData","publish_job","job_id","templateJobId","template_job_id","isActive","includes","isTerminal","canRetry","canCancel","getStatusBadgeClass","pending","running","failed","canceled","expired","getStatusIcon","getEvents","getFormattedDuration","duration","toFixed","getQueuePosition","hasExpired","expiresAt","Date","getRunnerId","getPayload","getMetadata","JobList","fetchByStatus","fetch","fetchByChannel","channel","fetchPending","fetchRunning","fetchCompleted","fetchFailed","fetchScheduled","scheduled","publish","jobData","getHealth","test","tests","clearStuck","clearChannel","confirm","cleanConsumers","purgeJobs","days_old","JobLog","JobLogList","JobEvent","JobEventList","JobsEngineStats","JobRunner","runner_id","idAttribute","ping","app","getApp","toISOString","ping_status","shutdown","graceful","getChannels","isHealthy","lastHeartbeat","now","getTime","getFormattedHeartbeatAge","heartbeatAge","getUtilization","min","getFormattedUptime","started","startTime","diffMs","diffSeconds","floor","getWorkerInfo","processed","alive","utilization","getDisplayName","runnerId","parts","split","length","canControl","JobRunnerList","getActive","where","runner","getByChannel","getHealthy","getTotalProcessed","models","reduce","getTotalFailed","getSystemHealth","aliveRunners","filter","getAllChannels","channels","Set","forEach","add","Array","from","sort","window","broadcast","command","broadcastStatus","broadcastShutdown","broadcastPause","broadcastResume","broadcastReload","Log","LogList","Member","hasPermission","permission","isArray","some","p","permissions","fetchForGroup","groupId","MemberList","MemberForms","MetricsPermission","id_key","MetricsPermissionList","PushDevice","PushDeviceList","PushTemplate","PushTemplateList","PushConfig","PushConfigList","PushDelivery","PushDeliveryList","PushConfigForms","rows","PushTemplateForms","defaultParams","is_active","GeoIPForms","readonly","step","GeoLocatedIP","lookup","ip","resp","EDIT_LOCATION_FORM","EDIT_SECURITY_FORM","EDIT_NETWORK_FORM","GeoLocatedIPList","Ticket","TicketList","TicketCategories","ticket","bug","feature","incident","security","fulfillment","new_user","new_group","qa","TicketCategoriesOptions","Object","entries","map","key","TicketForms","TicketNote","TicketNoteList","TableRow","ListViewItem","tagName","className","enableTooltips","actions","contextMenu","batchActions","tableView","listView","editingCells","buildRowTemplate","getResponsiveClasses","visibility","validBreakpoints","join","classes","push","show","isSelectable","column","combinedClasses","class","c","cellContent","buildCellTemplate","cellAction","rowAction","buildActionsTemplate","buildContextMenuTemplate","path","formatter","format","icon","buildContextMenuItems","menuItem","separator","divider","itemClass","danger","onAfterRender","cell","querySelector","context","row","table","index","innerHTML","selected","classList","setAttribute","onActionEditCell","stopPropagation","columnKey","getAttribute","find","col","has","enterEditMode","onActionRowClick","target","closest","onActionView","onActionEdit","onActionDelete","cellElement","contentSpan","currentValue","editor","createCellEditor","originalContent","style","display","editorContainer","document","createElement","appendChild","input","focus","select","dataset","setupEditorEvents","originalValue","editableOptions","createSelectEditor","createSwitchEditor","createTextareaEditor","createTextEditor","inputType","escapeHtml","optionsArray","optionsHtml","option","checked","saveBtn","cancelBtn","addEventListener","e","preventDefault","saveCellEdit","cancelCellEdit","autoSave","newValue","oldValue","exitEditMode","remove","displayValue","delete","div","textContent","addClass","selectCell","deselect","removeClass","LOOKUPS","exact","in","not","not_in","gt","gte","lt","lte","contains","icontains","startswith","istartswith","endswith","iendswith","isnull","val","range","parseFilterKey","paramKey","field","possibleLookup","slice","formatFilterDisplay","lookupDef","valueStr","String","values","v","trim","formattedValues","DjangoLookups","getLookupDescription","isValidLookup","hasOwnProperty","getAvailableLookups","keys","buildFilterKey","TableView","ListView","selectionMode","selectable","emptyMessage","addButtonIcon","isFullscreen","searchable","sortable","filterable","paginated","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","exportOptions","showExport","exportSource","filters","additionalFilters","hideActivePills","hideActivePillNames","batchBarLocation","addButtonLabel","toolbarButtons","tableOptions","striped","bordered","hover","responsive","searchPlacement","searchPlaceholder","initializeColumns","extractColumnFilters","footerTotalColumns","footer_total","hasFooterTotals","buildTableTemplate","setupCollectionListeners","collection","on","updateFooterTotals","charAt","parseColumnKey","fieldKey","totals","calculateFooterTotals","totalColumnIndex","safeKey","formatValue","sum","numValue","parseFloat","originalKey","batchPanelTop","buildBatchActionsPanel","batchPanelBottom","buildToolbarTemplate","__fs","fontSize","__val","buildTableClasses","buildTableHeaderTemplate","buildTableFooterTemplate","buildPaginationTemplate","background","buildActionButtonsTemplate","buildFilterDropdownTemplate","buildSearchTemplate","buttons","isFullscreenSupported","showAdd","dropdownItems","opt","button","handler","variant","checkPermissions","iconHtml","labelHtml","dataAttrs","btnClass","buildFilterList","allFilters","getAllAvailableFilters","activeFilters","getActiveFilters","activeClass","getFilterIcon","config","updateFilterPills","container","pillsHTML","buildActivePills","updateSearchInputs","searchInputs","querySelectorAll","hasSearch","search","toString","filterEntries","getFilterLabel","headerCells","currentSort","getSortBy","getSortDirection","sortIcon","getSortIcon","responsiveClasses","footerCells","actionsHTML","batchPanelTitle","_createItemView","itemTemplate","containerId","itemViews","_onItemSelect","updateBatchActionsPanel","_onItemDeselect","_onRowClick","bind","_onRowView","_onRowEdit","_onRowDelete","_onCellEdit","_onCellSave","_onCellCancel","onMounted","setupSearchClearListener","onActionClearSearch","onRowClick","getModelClass","getModelName","MODEL_NAME","replace","getItemViewClass","VIEW_CLASS","getAddFormConfig","getEditFormConfig","getFormDialogConfig","FORM_DIALOG_CONFIG","renderTemplateString","Mustache","onItemView","ViewClass","viewInstance","Dialog","showDialog","header","body","centered","showData","onItemEdit","formConfig","showModelForm","FormView","formFields","refresh","onItemDelete","DELETE_TEMPLATE","confirmText","confirmClass","destroy","fullscreenEnabled","mozFullScreenEnabled","webkitFullscreenEnabled","msFullscreenEnabled","onActionToggleFullscreen","exitFullscreen","enterFullscreen","requestFullscreen","mozRequestFullScreen","webkitRequestFullscreen","msRequestFullscreen","updateFullscreenButton","setupFullscreenListeners","mozCancelFullScreen","webkitExitFullscreen","msExitFullscreen","_fullscreenHandler","handleFullscreenChange","fullscreenElement","mozFullScreenElement","webkitFullscreenElement","msFullscreenElement","cleanupFullscreenListeners","removeEventListener","onActionRefresh","onActionAdd","onAdd","showForm","addRequiresActiveGroup","activeGroup","addRequiresActiveUser","user","activeUser","addFormDefaults","assign","onActionExport","source","download","onExport","toJSON","onActionApplySearch","searchTerm","setFilter","start","restEnabled","startsWith","direction","onActionSort","newSort","setParams","desc","sortField","a","b","aVal","bVal","updateSortIcons","currentSortField","currentSortDir","dropdown","isSorted","dropdownMenu","nextElementSibling","ascItem","descItem","noneItem","toggle","onActionSelectAll","isCurrentlyAllSelected","every","item","clearSelection","forEachItem","selectAllCell","onBeforeRender","searchValue","footerTotals","count","end","startEl","endEl","totalEl","pageSizeSelect","renderPagination","paginationContainer","currentPage","totalPages","ceil","prevPage","nextPage","pages","visibleSet","i","visible","last","onActionPage","rawPage","parseInt","max","page","isNaN","onChangePageSize","newSize","allParams","processedKeys","filterDef","startName","endName","fieldName","filterConfig","getFilterConfig","filterKey","f","additionalFilter","getFilterDisplayValue","date","daterange","number","boolean","onActionAddFilter","buildFilterDialogField","newFilterValue","extractFilterValue","applyFilters","placeHolder","displayFormat","startDate","endDate","valueArray","formResult","filter_value","onActionEditFilter","onActionRemoveFilter","onActionClearAllFilters","selectedCount","getSelectedItems","panel","countEl","allSelected","someSelected","onActionBatch","batchAction","selectedItems","items","onActionClearSelection","onActionCustomToolbarButton","buttonIndex","call","TablePage","Page","pageName","defaultQuery","groupField","tableViewConfig","formCreate","formEdit","tableViewOptions","urlSyncEnabled","lastUpdated","isLoading","buildTemplate","onInit","applyQueryToCollection","fetchOnMount","addChild","setupEventListeners","toLocaleTimeString","updateStatusDisplay","syncUrl","handleFilterEdit","query","reservedParams","JSON","parse","force","router","currentUrl","URL","location","currentParams","searchParams","desiredParams","collectionParams","stringify","hasChanges","updateBrowserUrl","updatedElement","countElement","onEnter","requiresGroup","clearAllFilters","onGroupChange","onBeforeDestroy","off","showStatus","TabView","tabs","activeTab","tabsClass","contentClass","minWidth","enableResponsive","tabPadding","dropdownStyle","viewOptions","tabLabels","currentMode","tabWidthCache","Map","lastContainerWidth","resizeObserver","_measurementSpan","_tabComputedStyle","isMobileMode","hasOverflow","view","addTab","handleResize","renderTemplate","buildTabNavigation","buildTabContent","buildDropdownNavigation","buildTabsNavigation","tabItems","tabId","getTabId","activeLabel","buttonHtml","buildMobileDropdownNavigation","shouldUseMobileDropdown","enableMobileDropdown","viewportWidth","innerWidth","mobileBreakpoint","tabPanes","toLowerCase","showTab","tabLabel","activeView","isMounted","previousTab","updateTabNavigation","updateTabContent","activeTabLabel","previousTabLabel","reRenderNavigation","prevTabButton","activeTabButton","activeTabId","previousTabId","prevPane","activePane","onTabActivated","onActionShowTab","_initializeMeasurementStyles","tabButton","getComputedStyle","font","letterSpacing","paddingLeft","paddingRight","setupResponsiveHandling","onAfterMount","disconnect","parentElement","removeChild","getActiveTab","getTabLabels","getTab","makeActive","hasPerm","parent","removeTab","calculateTabWidth","estimatedWidth","position","whiteSpace","span","fontFamily","width","offsetWidth","getTotalTabWidth","getContainerWidth","shouldUseDropdown","containerWidth","totalTabWidth","updateNavigationMode","ResizeObserver","observe","abs","newMode","mode","navigationContainer","newNavigation","outerHTML","getNavigationMode","setNavigationMode","clearWidthCache","clear","FilePreviewView","isPdf","thumbnailUrl","onActionViewFile","LightboxGallery","MOJO","plugins","src","alt","PDFViewer","ChatMessageView","theme","isCurrentUser","getBubblesTemplate","getCompactTemplate","attachments","attachmentsContainer","filePreview","ChatInputView","buttonText","pendingUploads","enableFileDrop","dropZoneSelector","multiple","acceptedTypes","visualFeedback","dragOverClass","dragActiveClass","textarea","autoResizeTextarea","handleKeydown","shiftKey","onActionSendMessage","onFileDrop","files","uploadFile","uploadId","random","addFilePreview","updateFileProgress","uploadResult","handleUploadComplete","handleUploadError","preview","formatFileSize","progressBar","progressContainer","onActionRemoveAttachment","setBusy","busy","spinner","clearInput","height","scrollHeight","bytes","log","pow","applyFileDropMixin","ChatView","adapter","currentUserId","inputPlaceholder","inputButtonText","messages","messageViews","inputView","handleSendMessage","_buildMessageViews","_renderChildren","scrollToBottom","messagesContainer","messageView","_createMessageView","author","addMessage","scroll","addNote","requestAnimationFrame","scrollTop","clearMessages","level","example","clone"],"mappings":"0OAOA,MAAMA,iBAAiBC,EAAAA,MACnB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,sBAElB,EAMJ,MAAMC,qBAAqBC,EAAAA,WACvB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYT,SACZK,SAAU,qBACVK,KAAM,MACHF,GAEX,ECPJ,MAAMG,oBAAoBV,EAAAA,MACxB,WAAAC,CAAYC,EAAO,GAAIK,EAAU,CAAA,GAC/BJ,MAAMD,EAAM,CACVE,SAAU,2BACPG,GAEP,CAmBA,aAAMI,CAAQT,EAAO,GAAIK,EAAU,CAAA,GACjC,IAAKK,KAAKC,GAER,aADMD,KAAKE,UAAU,oCACd,CACLC,SAAS,EACTC,OAAQ,IACRC,MAAO,qBAIX,IACE,MAAMC,EAAM,GAAGN,KAAKO,SAASP,KAAKC,cAKlC,aAJuBD,KAAKQ,KAAKC,KAAKH,EAAKhB,EAAMK,EAAQe,OAK3D,OAASC,GACP,MAAO,CACLR,SAAS,EACTC,OAAQO,GAAKP,QAAU,IACvBC,MAAOM,GAAKC,SAAW,2BAE3B,CACF,CAYA,WAAMC,CAAMlB,EAAU,IACpB,IAAKK,KAAKC,GAER,aADMD,KAAKE,UAAU,kCACd,CACLC,SAAS,EACTC,OAAQ,IACRC,MAAO,qBAIX,MAAMS,GAAUnB,EAAQmB,QAAU,OAAOC,cACnCT,EAAM,GAAGN,KAAKO,SAASP,KAAKC,YAElC,IACE,MAAe,SAAXa,QACWd,KAAKQ,KAAKC,KAAKH,EAAKX,EAAQL,MAAQ,CAAA,EAAIK,EAAQe,cAElDV,KAAKQ,KAAKQ,IAAIV,EAAKX,EAAQe,OAC1C,OAASC,GACP,MAAO,CACLR,SAAS,EACTC,OAAQO,GAAKP,QAAU,IACvBC,MAAOM,GAAKC,SAAW,yBAE3B,CACF,CAUA,eAAMK,CAAU3B,EAAO,GAAIK,EAAU,CAAA,GACnC,IAAKK,KAAKC,GAER,aADMD,KAAKE,UAAU,sCACd,CACLC,SAAS,EACTC,OAAQ,IACRC,MAAO,qBAIX,IACE,MAAMC,EAAM,GAAGN,KAAKO,SAASP,KAAKC,gBAClC,aAAaD,KAAKQ,KAAKC,KAAKH,EAAKhB,EAAMK,EAAQe,OACjD,OAASC,GACP,MAAO,CACLR,SAAS,EACTC,OAAQO,GAAKP,QAAU,IACvBC,MAAOM,GAAKC,SAAW,6BAE3B,CACF,CAQA,wBAAaM,CAAYjB,EAAIX,EAAO,CAAA,EAAIK,EAAU,CAAA,GAChD,MAAMwB,EAAQ,IAAIrB,YAAY,CAAEG,MAAMN,GACtC,aAAawB,EAAMpB,QAAQT,EAAMK,EACnC,CAOA,sBAAayB,CAAUnB,EAAIN,EAAU,IACnC,MAAMwB,EAAQ,IAAIrB,YAAY,CAAEG,MAAMN,GACtC,aAAawB,EAAMN,MAAMlB,EAC3B,CAQA,0BAAa0B,CAAcpB,EAAIX,EAAO,CAAA,EAAIK,EAAU,CAAA,GAClD,MAAMwB,EAAQ,IAAIrB,YAAY,CAAEG,MAAMN,GACtC,aAAawB,EAAMF,UAAU3B,EAAMK,EACrC,EAOF,MAAM2B,wBAAwB5B,EAAAA,WAC5B,WAAAL,CAAYM,EAAU,IACpBJ,MAAM,CACJK,WAAYE,YACZN,SAAU,wBACVK,KAAM,MACHF,GAEP,EAyRF,MAAM4B,gBAAgBnC,EAAAA,MAClB,WAAAC,CAAYC,EAAO,GAAIK,EAAU,CAAA,GAC7BJ,MAAMD,EAAM,CACRE,SAAU,4BACPG,GAEX,EAGJ4B,QAAQC,UAAYC,eAAenC,GAC/B,aAAakB,EAAAA,KAAKC,KAAK,sBAAuBnB,EAClD,EAKA,MAAMoC,oBAAoBhC,EAAAA,WACxB,WAAAL,CAAYM,EAAU,IACpBJ,MAAM,CACJK,WAAY2B,QACZ/B,SAAU,yBACVK,KAAM,MACHF,GAEP,EAMG,MAACgC,EAAe,CACnBC,OAAQ,CACNC,MAAO,cACPC,OAAQ,CACN,CACIC,KAAM,aACNC,KAAM,SACNC,MAAO,SACPvC,WAAY4B,gBACZY,WAAY,OACZC,WAAY,KACZC,SAAU,GACVC,YAAa,oBACbC,YAAY,EACZC,UAAU,EACVC,WAAY,IACZC,QAAS,IAEb,CACET,KAAM,QACND,KAAM,QACNE,MAAO,gBACPI,YAAa,sBACbE,UAAU,EACVE,QAAS,IAEX,CACET,KAAM,gBACND,KAAM,SACNE,MAAO,gBACPQ,QAAS,GAEX,CACET,KAAM,iBACND,KAAM,SACNE,MAAO,iBACPS,cAAc,EACdD,QAAS,GAEX,CACET,KAAM,oBACND,KAAM,SACNE,MAAO,iBACPQ,QAAS,GAEX,CACET,KAAM,oBACND,KAAM,SACNE,MAAO,iBACPQ,QAAS,GAEX,CACET,KAAM,gBACND,KAAM,OACNE,MAAO,2BACPI,YAAa,iCACbI,QAAS,GACTE,KAAM,iEAKZC,KAAM,CACJf,MAAO,eACPC,OAAQ,CACN,CACEE,KAAM,QACND,KAAM,QACNE,MAAO,gBACPM,UAAU,EACVE,QAAS,IAEX,CACET,KAAM,gBACND,KAAM,SACNE,MAAO,gBACPQ,QAAS,GAEX,CACET,KAAM,iBACND,KAAM,SACNE,MAAO,iBACPQ,QAAS,GAEX,CACET,KAAM,oBACND,KAAM,SACNE,MAAO,iBACPQ,QAAS,GAEX,CACET,KAAM,oBACND,KAAM,SACNE,MAAO,iBACPQ,QAAS,GAEX,CACET,KAAM,gBACND,KAAM,OACNE,MAAO,2BACPI,YAAa,iCACbI,QAAS,OAUjB,MAAMI,oBAAoBzD,EAAAA,MACxB,WAAAC,CAAYC,EAAO,GAAIK,EAAU,CAAA,GAC/BJ,MAAMD,EAAM,CACVE,SAAU,yBACPG,GAEP,EAMF,MAAMmD,wBAAwBpD,EAAAA,WAC5B,WAAAL,CAAYM,EAAU,IACpBJ,MAAM,CACJK,WAAYiD,YACZrD,SAAU,sBACVK,KAAM,MACHF,GAEP,EA4BF,MAAMoD,sBAAsB3D,EAAAA,MAC1B,WAAAC,CAAYC,EAAO,GAAIK,EAAU,CAAA,GAC/BJ,MAAMD,EAAM,CACVE,SAAU,6BACPG,GAEP,EAMF,MAAMqD,0BAA0BtD,EAAAA,WAC9B,WAAAL,CAAYM,EAAU,IACpBJ,MAAM,CACJK,WAAYmD,cACZvD,SAAU,0BACVK,KAAM,MACHF,GAEP,ECloBF,MAAMsD,qBAAqBC,EAAAA,KACvB,WAAA7D,CAAYM,EAAU,IAClBJ,MAAM,CACF4D,SAAU,4BACPxD,IAIPK,KAAKoD,SAAWzD,EAAQyD,UAAY,eACpCpD,KAAKqD,SAAW1D,EAAQ0D,UAAY,EACpCrD,KAAKsD,kBAAoBC,EAAAA,cAAcC,KAAKxD,KAAKqD,SAAU,YAG3DrD,KAAKyD,SAAW,EAChBzD,KAAK0D,WAAa,EAClB1D,KAAK2D,OAAS,EACd3D,KAAK4D,MAAQ5D,KAAKqD,SAClBrD,KAAK6D,gBAAkB,MACvB7D,KAAK8D,eAAiB9D,KAAKsD,kBAC3BtD,KAAKI,OAAS,qBAGdJ,KAAK+D,YAAoC,IAAvBpE,EAAQoE,WAC1B/D,KAAKgE,SAAWrE,EAAQqE,UAAY,KAGpChE,KAAKiE,WAAY,EACjBjE,KAAKkE,WAAY,CACrB,CAKA,WAAAC,GACI,MAAO,iuDAwCX,CAUA,cAAAC,CAAeC,GACPrE,KAAKiE,WAAajE,KAAKkE,YAI3BlE,KAAKyD,SAAWY,EAAaZ,SAC7BzD,KAAK0D,WAAaW,EAAaX,WAC/B1D,KAAK2D,OAASU,EAAaV,OAC3B3D,KAAK4D,MAAQS,EAAaT,OAAS5D,KAAKqD,SAGxCrD,KAAK6D,gBAAkBN,EAAAA,cAAcC,KAAKxD,KAAK2D,OAAQ,YACvD3D,KAAK8D,eAAiBP,EAAAA,cAAcC,KAAKxD,KAAK4D,MAAO,YAGjD5D,KAAK0D,WAAa,IAClB1D,KAAKI,OAAS,gBAAgBJ,KAAK0D,cAEnC1D,KAAKI,OAAS,uBAIlBJ,KAAKsE,SACT,CAMA,aAAAC,CAAc3D,EAAU,qBACpBZ,KAAKkE,WAAY,EACjBlE,KAAKyD,SAAW,EAChBzD,KAAK0D,WAAa,IAClB1D,KAAKI,OAASQ,EACdZ,KAAKsE,QACT,CAMA,UAAAE,CAAW5D,EAAU,iBACjBZ,KAAKI,OAASQ,EACdZ,KAAKsE,QACT,CAKA,aAAAG,GACIzE,KAAKiE,WAAY,EACjBjE,KAAKI,OAAS,mBACdJ,KAAKsE,QACT,CAQA,oBAAMI,CAAeC,EAAQC,EAAOC,GAChC,IAAI7E,KAAKiE,YAAajE,KAAKkE,YAK3BW,EAAQC,UAAW,EAGnB9E,KAAKyE,gBAGLzE,KAAK+E,KAAK,UAGmB,mBAAlB/E,KAAKgE,UACZ,UACUhE,KAAKgE,UACf,OAAS3D,GACL2E,QAAQ3E,MAAM,4BAA6BA,EAC/C,CAER,CAMA,WAAA4E,CAAY7B,GACRpD,KAAKoD,SAAWA,EAChBpD,KAAKsE,QACT,CAMA,WAAAY,CAAYrF,GACRG,KAAKqD,SAAWxD,EAChBG,KAAKsD,kBAAoBC,EAAAA,cAAcC,KAAK3D,EAAM,YAClDG,KAAK4D,MAAQ/D,EACbG,KAAK8D,eAAiB9D,KAAKsD,kBAC3BtD,KAAKsE,QACT,CAMA,aAAAa,GACI,OAAOnF,KAAK0D,UAChB,CAMA,WAAA0B,GACI,OAAOpF,KAAKkE,SAChB,CAMA,WAAAmB,GACI,OAAOrF,KAAKiE,SAChB,CAMA,QAAAqB,GACI,MAAO,CACHlC,SAAUpD,KAAKoD,SACfC,SAAUrD,KAAKqD,SACfI,SAAUzD,KAAKyD,SACfC,WAAY1D,KAAK0D,WACjBC,OAAQ3D,KAAK2D,OACbC,MAAO5D,KAAK4D,MACZK,UAAWjE,KAAKiE,UAChBC,UAAWlE,KAAKkE,UAChB9D,OAAQJ,KAAKI,OAErB,ECvOJ,MAAMmF,WACF,WAAAlG,CAAYmG,EAAW7F,EAAU,IAe7B,GAdAK,KAAKwF,UAAYA,EACjBxF,KAAKL,QAAU,CACX8F,KAAM,KACNzD,KAAM,KACN0D,MAAO,KACPC,YAAa,KACbC,WAAY,KACZC,WAAY,KACZC,QAAS,KACTC,WAAW,KACRpG,KAIFK,KAAKL,QAAQ8F,MAAUzF,KAAKL,QAAQ8F,gBAAgBO,MACrD,MAAM,IAAIC,MAAM,2CAIpBjG,KAAKiE,WAAY,EACjBjE,KAAKkG,cAAgB,KACrBlG,KAAKmG,cAAgB,KACrBnG,KAAKoG,aAAe,KACpBpG,KAAKqG,aAAe,KAGhBrG,KAAKL,QAAQoG,YACb/F,KAAKqG,aAAe,IAAIC,gBAI5BtG,KAAKuG,QAAUvG,KAAKwG,cACxB,CAOA,kBAAMA,GACF,IAMI,IAAIC,EAiBAC,EAtBA1G,KAAKL,QAAQoG,WACb/F,KAAK2G,qBAKT,IACIF,QAAmBzG,KAAK4G,iBAC5B,OAASvG,GACL,MAAM,IAAI4F,MAAM,8BAA8B5F,EAAMO,UACxD,CAEA,GAAIZ,KAAKiE,UACL,MAAM,IAAIgC,MAAM,oBAIpB,IAAKQ,IAAeA,EAAWI,WAC3B,MAAM,IAAIZ,MAAM,+CAKpB,IACIS,QAAe1G,KAAK8G,eAAeL,EAAWI,WAClD,OAASxG,GACL,MAAM,IAAI4F,MAAM,uBAAuB5F,EAAMO,UACjD,CAEA,GAAIZ,KAAKiE,UACL,MAAM,IAAIgC,MAAM,oBAIpB,UACUjG,KAAK+G,iBACf,OAAS1G,GACL2E,QAAQgC,KAAK,sCAAuC3G,EAGxD,CAIA,OADAL,KAAKiH,YAAYjH,KAAKwF,WACfxF,KAAKwF,SAEhB,OAASnF,GAIL,KAHsB,qBAAlBA,EAAMO,SACNZ,KAAKkH,SAAS7G,GAEZA,CACV,CACJ,CAOA,qBAAMuG,GACF,IACI,MAAMO,EAAU,CACZ/D,SAAUpD,KAAKL,QAAQqC,MAAQhC,KAAKL,QAAQ8F,KAAKzD,KACjDoF,UAAWpH,KAAKL,QAAQ8F,KAAK5F,KAC7BwH,aAAcrH,KAAKL,QAAQ8F,KAAK1D,MAGhC/B,KAAKL,QAAQ+F,QAAOyB,EAAQzB,MAAQ1F,KAAKL,QAAQ+F,OACjD1F,KAAKL,QAAQgG,cAAawB,EAAQxB,YAAc3F,KAAKL,QAAQgG,aAEjE,MAAM2B,QAAiBtH,KAAKwF,UAAUhF,KAAKC,KAAK,+BAAgC0G,GAEhF,IAAKG,EACD,MAAM,IAAIrB,MAAM,0CAGpB,IAAKqB,EAAShI,KACV,MAAM,IAAI2G,MAAM,2CAIpB,IAAKqB,EAAShI,KAAKc,OAAQ,CACvB,MAAMmH,EAAeD,EAAShI,KAAKe,OAAS,2BAC5C,MAAM,IAAI4F,MAAMsB,EACpB,CAEA,IAAKD,EAAShI,KAAKA,KACf,MAAM,IAAI2G,MAAM,mDAQpB,OAJIqB,EAAShI,KAAKA,KAAKW,IACnBD,KAAKwF,UAAUgC,IAAI,KAAMF,EAAShI,KAAKA,KAAKW,IAGzCqH,EAAShI,KAAKA,IAEzB,OAASe,GAEL,GAAsB,kBAAlBA,EAAMO,SAA8C,cAAfP,EAAM2B,KAC3C,MAAM,IAAIiE,MAAM,yEAEpB,MAAM5F,CACV,CACJ,CAQA,oBAAMyG,CAAeW,GACjB,OAAO,IAAIC,QAAQ,CAACC,EAASC,KAEzB,KAAM5H,KAAKL,QAAQ8F,gBAAgBO,MAE/B,YADA4B,EAAO,IAAI3B,MAAM,2CAIrB,MAAM4B,EAAM,IAAIC,eAChB9H,KAAKkG,cAAgB2B,EAGrBA,EAAIE,OAAOC,WAAcpD,IACrB,GAAI5E,KAAKiE,UAAW,OAEpB,MAAMI,EAAe,CACjBZ,SAAUmB,EAAMjB,OAASiB,EAAMhB,MAC/BD,OAAQiB,EAAMjB,OACdC,MAAOgB,EAAMhB,MACbF,WAAYuE,KAAKC,MAAOtD,EAAMjB,OAASiB,EAAMhB,MAAS,MAG1D5D,KAAKmI,YAAY9D,IAIrBwD,EAAIO,OAAS,KACLP,EAAIzH,QAAU,KAAOyH,EAAIzH,OAAS,IAClCuH,EAAQ,CACJrI,KAAMuI,EAAIP,SACVlH,OAAQyH,EAAIzH,OACZiI,WAAYR,EAAIQ,WAChBR,QAGJD,EAAO,IAAI3B,MAAM,kBAAkB4B,EAAIzH,UAAUyH,EAAIQ,gBAI7DR,EAAIS,QAAU,KACVV,EAAO,IAAI3B,MAAM,kCAGrB4B,EAAIU,UAAY,KACZX,EAAO,IAAI3B,MAAM,4BAGrB4B,EAAIW,QAAU,KACVZ,EAAO,IAAI3B,MAAM,sBAGrB4B,EAAIU,UAAY,KACZX,EAAO,IAAI3B,MAAM,mEAIrB4B,EAAIY,KAAK,MAAOhB,GAChBI,EAAIa,iBAAiB,eAAgB1I,KAAKL,QAAQ8F,KAAK1D,MAGvD8F,EAAIc,QAAU,IAGdd,EAAIe,KAAK5I,KAAKL,QAAQ8F,OAE9B,CAOA,qBAAMsB,GACF,IACI,MAAMO,QAAiBtH,KAAKwF,UAAUqD,KAAK,CAAElE,OAAQ,sBAErD,IAAK2C,EACD,MAAM,IAAIrB,MAAM,0CAIpB,GAAIqB,EAAShI,OAASgI,EAAShI,KAAKc,OAAQ,CACxC,MAAMmH,EAAeD,EAAShI,KAAKe,OAAS,qCAC5C,MAAM,IAAI4F,MAAMsB,EACpB,CAEA,OAAOD,CACX,OAASjH,GAEL,GAAsB,kBAAlBA,EAAMO,SAA8C,cAAfP,EAAM2B,KAC3C,MAAM,IAAIiE,MAAM,oFAEpB,MAAM5F,CACV,CACJ,CAOA,WAAA8H,CAAY9D,GAEJrE,KAAKmG,eAAiBnG,KAAKmG,cAAc/B,gBACzCpE,KAAKmG,cAAc/B,eAAeC,GAIC,mBAA5BrE,KAAKL,QAAQiG,YACpB5F,KAAKL,QAAQiG,WAAWvB,EAEhC,CAOA,WAAA4C,CAAYP,GAEJ1G,KAAKoG,cACLpG,KAAKoG,aAAa7B,cAAc,kCAIhCvE,KAAKmG,eACL2C,WAAW,KACP,IACQ9I,KAAKmG,eAAoD,mBAA5BnG,KAAKmG,cAAc4C,MAChD/I,KAAKmG,cAAc4C,MAE3B,OAAS1I,GACL2E,QAAQgC,KAAK,+BAAgC3G,EACjD,GACD,KAIgC,mBAA5BL,KAAKL,QAAQkG,YACpB7F,KAAKL,QAAQkG,WAAWa,EAEhC,CAOA,QAAAQ,CAAS7G,GAEL,GAAIL,KAAKmG,cACL,IACInG,KAAKmG,cAAc4C,MACvB,OAAS1I,GACL2E,QAAQgC,KAAK,wCAAyC3G,EAC1D,CAIAL,KAAKqG,cACLrG,KAAKqG,aAAahG,MAAM,kBAAkBA,EAAMO,WAIhB,mBAAzBZ,KAAKL,QAAQmG,SACpB9F,KAAKL,QAAQmG,QAAQzF,EAE7B,CAMA,kBAAAsG,GAEI3G,KAAKoG,aAAe,IAAInD,aAAa,CACjCG,SAAUpD,KAAKL,QAAQqC,MAAQhC,KAAKL,QAAQ8F,KAAKzD,KACjDqB,SAAUrD,KAAKL,QAAQ8F,KAAK5F,KAC5BkE,YAAY,EACZC,SAAU,IAAMhE,KAAKgJ,WAIzBhJ,KAAKmG,cAAgBnG,KAAKqG,aAAa4C,SAASjJ,KAAKoG,aAAc,OAAQ,CACvEvE,MAAO,cACPqH,UAAU,EACVC,aAAa,GAErB,CAMA,MAAAH,GACI,OAAIhJ,KAAKiE,YAITjE,KAAKiE,WAAY,EAGbjE,KAAKkG,eAAqD,mBAA7BlG,KAAKkG,cAAckD,OAChDpJ,KAAKkG,cAAckD,QAInBpJ,KAAKoG,cACLpG,KAAKoG,aAAa3B,gBAIlBzE,KAAKmG,eACL2C,WAAW,KACP,IACQ9I,KAAKmG,eAAoD,mBAA5BnG,KAAKmG,cAAc4C,MAChD/I,KAAKmG,cAAc4C,MAE3B,OAAS1I,GACL2E,QAAQgC,KAAK,yCAA0C3G,EAC3D,GACD,OAGA,EACX,CAMA,WAAAgF,GACI,OAAOrF,KAAKiE,SAChB,CAQA,IAAAoF,CAAKC,EAAWxD,GACZ,OAAO9F,KAAKuG,QAAQ8C,KAAKC,EAAWxD,EACxC,CAOA,MAAMA,GACF,OAAO9F,KAAKuG,QAAQgD,MAAMzD,EAC9B,CAOA,QAAQ0D,GACJ,OAAOxJ,KAAKuG,QAAQkD,QAAQD,EAChC,CAMA,QAAAlE,GACI,MAAO,CACHlC,SAAUpD,KAAKL,QAAQ8F,KAAKzD,KAC5BnC,KAAMG,KAAKL,QAAQ8F,KAAK5F,KACxBkC,KAAM/B,KAAKL,QAAQ8F,KAAK1D,KACxBkC,UAAWjE,KAAKiE,UAChByB,MAAO1F,KAAKL,QAAQ+F,MACpBC,YAAa3F,KAAKL,QAAQgG,YAElC,ECncJ,MAAM+D,oBAAoBtK,EAAAA,MACtB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,wBAElB,EAGJ,MAAMmK,wBAAwBjK,EAAAA,WAC1B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAY8J,YACZlK,SAAU,uBACVK,KAAM,MACHF,GAEX,EAGC,MAACiK,EAAmB,CACrBhI,OAAQ,CACJC,MAAO,sBACPC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,eACPI,YAAa,qBACbwH,KAAM,IAEV,CACI7H,KAAM,cACND,KAAM,OACNE,MAAO,cACPM,UAAU,EACVuH,MAAO,iCACPzH,YAAa,mCACbM,KAAM,6CACNkH,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,SACNE,MAAO,wBACP6H,MAAO,YACPnK,QAAS,CACL,CAAEmK,MAAO,GAAIC,KAAM,kBACnB,CAAED,MAAO,YAAaC,KAAM,yBAC5B,CAAED,MAAO,YAAaC,KAAM,kBAC5B,CAAED,MAAO,YAAaC,KAAM,2BAC5B,CAAED,MAAO,YAAaC,KAAM,oBAC5B,CAAED,MAAO,eAAgBC,KAAM,oBAC/B,CAAED,MAAO,YAAaC,KAAM,oBAC5B,CAAED,MAAO,YAAaC,KAAM,mBAC5B,CAAED,MAAO,YAAaC,KAAM,kBAC5B,CAAED,MAAO,eAAgBC,KAAM,sBAC/B,CAAED,MAAO,aAAcC,KAAM,sBAC7B,CAAED,MAAO,aAAcC,KAAM,kBAC7B,CAAED,MAAO,iBAAkBC,KAAM,0BAErCtH,QAAS,GACTE,KAAM,wDAEV,CACIX,KAAM,UACND,KAAM,OACNE,MAAO,qBACPI,YAAa,yCACbI,QAAS,GACTE,KAAM,yCAEV,CACIX,KAAM,aACND,KAAM,OACNE,MAAO,wBACPI,YAAa,4CACbI,QAAS,GACTE,KAAM,4CAEV,CACIX,KAAM,aACND,KAAM,SACNE,MAAO,aACP4H,KAAM,GAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP+H,SAAS,EACTH,KAAM,KAKlBjH,KAAM,CACFf,MAAO,uBACPC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,eACPI,YAAa,qBACbwH,KAAM,IAEV,CACI7H,KAAM,cACND,KAAM,OACNE,MAAO,cACPM,UAAU,EACVF,YAAa,mCACbM,KAAM,6CACNkH,KAAM,IAEV,CACI7H,KAAM,kBACND,KAAM,OACNE,MAAO,yBACP4H,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,SACNE,MAAO,aACP4H,KAAM,GAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP+H,SAAS,EACTH,KAAM,KAKlBI,OAAQ,CACJnI,OAAQ,CACJ,CACIC,KAAM,aACNC,KAAM,QACNC,MAAO,gBACPvC,WAAYwK,EAAAA,UACZhI,WAAY,OACZC,WAAY,KACZC,SAAU,GACVC,YAAa,mBACbC,YAAY,EACZE,WAAY,KAEhB,CACIT,KAAM,aACNC,KAAM,OACNC,MAAO,eACPvC,WAAYyK,EAAAA,SACZjI,WAAY,eACZC,WAAY,KACZC,SAAU,GACVC,YAAa,kBACbC,YAAY,EACZE,WAAY,OAKxB4H,YAAa,CACTtI,OAAQ,CACJ,CACIE,KAAM,aACND,KAAM,SACNE,MAAO,wBACP6H,MAAO,YACPnK,QAAS,CACL,CAAEmK,MAAO,GAAIC,KAAM,kBACnB,CAAED,MAAO,YAAaC,KAAM,yBAC5B,CAAED,MAAO,YAAaC,KAAM,kBAC5B,CAAED,MAAO,YAAaC,KAAM,2BAC5B,CAAED,MAAO,YAAaC,KAAM,oBAC5B,CAAED,MAAO,eAAgBC,KAAM,oBAC/B,CAAED,MAAO,YAAaC,KAAM,oBAC5B,CAAED,MAAO,YAAaC,KAAM,mBAC5B,CAAED,MAAO,YAAaC,KAAM,kBAC5B,CAAED,MAAO,eAAgBC,KAAM,sBAC/B,CAAED,MAAO,aAAcC,KAAM,sBAC7B,CAAED,MAAO,aAAcC,KAAM,kBAC7B,CAAED,MAAO,iBAAkBC,KAAM,0BAErCtH,QAAS,GACTE,KAAM,wDAEV,CACIX,KAAM,UACND,KAAM,OACNE,MAAO,qBACPI,YAAa,yCACbI,QAAS,GACTE,KAAM,yCAEV,CACIX,KAAM,aACND,KAAM,OACNE,MAAO,wBACPI,YAAa,4CACbI,QAAS,GACTE,KAAM,qDAStB,cAAmBvD,EAAAA,MACf,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,qBAElB,CAEA,OAAA6K,GACI,MAAgC,UAAzBrK,KAAKsK,IAAI,WACpB,CAiBA,MAAAvC,CAAOpI,EAAU,IACb,OAAO,IAAI4F,WAAWvF,KAAML,EAChC,GAGJ,MAAM4K,iBAAiB7K,EAAAA,WACnB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYoG,EACZxG,SAAU,oBACVK,KAAM,MACHF,GAEX,EC9PH,MAAM6K,sBAAsBpL,EAAAA,MACxB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,sBACViL,YAAY,GAEpB,EAIL,MAAMC,sBAAsBtL,EAAAA,MACxB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,uBAElB,EAGJ,MAAMmL,0BAA0BjL,EAAAA,WAC5B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAY8K,cACZlL,SAAU,sBACVK,KAAM,MACHF,GAEX,EAGC,MAACiL,EAAqB,CACvBhI,KAAM,CACFf,MAAO,sBACPC,OAAQ,CACJ,CACIE,KAAM,WACND,KAAM,SACNE,MAAO,WACPI,YAAa,kBACb1C,QAAS,IAAMkL,SAASC,WACxBC,UAAU,EACVC,WAAW,EACXnB,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,OACNE,MAAO,WACPI,YAAa,oBACbwH,KAAM,GAEV,CACI7H,KAAM,cACND,KAAM,WACNE,MAAO,cACPI,YAAa,oBACbwH,KAAM,IAEV,CACI7H,KAAM,UACND,KAAM,WACNE,MAAO,UACPI,YAAa,gBACbwH,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,OACNE,MAAO,YACPI,YAAa,kBACbwH,KAAM,GAEV,CACI7H,KAAM,eACND,KAAM,OACNE,MAAO,eACPI,YAAa,qBACbwH,KAAM,MAStB,MAAMgB,iBAAiBzL,EAAAA,MACnB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,0BAElB,EAGJ,MAAMyL,qBAAqBvL,EAAAA,WACvB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYiL,SACZrL,SAAU,yBACVK,KAAM,MACHF,GAEX,EA+HJ,MAAMuL,wBAAwB9L,EAAAA,MAC1B,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,+BAElB,EAGJ,MAAM2L,4BAA4BzL,EAAAA,WAC9B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYsL,gBACZ1L,SAAU,8BACVK,KAAM,MACHF,GAEX,EAGJ,MAAMyL,qBAAqBhM,EAAAA,MACvB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,oCAElB,EAGJ,MAAM6L,yBAAyB3L,EAAAA,WAC3B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYwL,aACZ5L,SAAU,mCACVK,KAAM,MACHF,GAEX,EAMJ,MAAM2L,wBAAwBlM,EAAAA,MAC1B,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,kCAElB,EAGJ,MAAM+L,4BAA4B7L,EAAAA,WAC9B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAY0L,gBACZ9L,SAAU,iCACVK,KAAM,MACHF,GAEX,EAwBC,MAAC6L,EAAkB,CACpB,CAAE1B,MAAO,EAAG7H,MAAO,eACnB,CAAE6H,MAAO,EAAG7H,MAAO,eACnB,CAAE6H,MAAO,EAAG7H,MAAO,iBACnB,CAAE6H,MAAO,EAAG7H,MAAO,sBACnB,CAAE6H,MAAO,EAAG7H,MAAO,gBACnB,CAAE6H,MAAO,EAAG7H,MAAO,4BACnB,CAAE6H,MAAO,EAAG7H,MAAO,iCACnB,CAAE6H,MAAO,EAAG7H,MAAO,6BACnB,CAAE6H,MAAO,EAAG7H,MAAO,kCACnB,CAAE6H,MAAO,EAAG7H,MAAO,4BAIjBwJ,EAAiB,CACnB,CAAE3B,MAAO,EAAG7H,MAAO,8BACnB,CAAE6H,MAAO,EAAG7H,MAAO,yBAIjByJ,EAAoB,CACtB,CAAE5B,MAAO,KAAM7H,MAAO,cACtB,CAAE6H,MAAO,KAAM7H,MAAO,cACtB,CAAE6H,MAAO,IAAK7H,MAAO,oBACrB,CAAE6H,MAAO,KAAM7H,MAAO,8BACtB,CAAE6H,MAAO,IAAK7H,MAAO,iBACrB,CAAE6H,MAAO,KAAM7H,MAAO,2BACtB,CAAE6H,MAAO,WAAY7H,MAAO,YAC5B,CAAE6H,MAAO,QAAS7H,MAAO,uBAIvB0J,EAAmB,CACrB,CAAE7B,MAAO,MAAO7H,MAAO,UACvB,CAAE6H,MAAO,MAAO7H,MAAO,WACvB,CAAE6H,MAAO,QAAS7H,MAAO,SACzB,CAAE6H,MAAO,OAAQ7H,MAAO,YAItB2J,EAAoB,CACtB,CAAE9B,MAAO,QAAS7H,MAAO,QAAS0D,YAAa,4CAA6CkG,KAAM,CAAE9J,KAAM,QAC1G,CAAE+H,MAAO,YAAa7H,MAAO,oBAAqB0D,YAAa,iCAAkCkG,KAAM,CAAE9J,KAAM,QAC/G,CAAE+H,MAAO,UAAW7H,MAAO,UAAW0D,YAAa,0BAA2BkG,KAAM,CAAE9J,KAAM,QAC5F,CAAE+H,MAAO,WAAY7H,MAAO,WAAY0D,YAAa,gCAAiCkG,KAAM,CAAE9J,KAAM,QACpG,CAAE+H,MAAO,YAAa7H,MAAO,YAAa0D,YAAa,wBAAyBkG,KAAM,CAAE9J,KAAM,QAC9F,CAAE+H,MAAO,eAAgB7H,MAAO,eAAgB0D,YAAa,uBAAwBkG,KAAM,CAAE9J,KAAM,QACnG,CAAE+H,MAAO,WAAY7H,MAAO,WAAY0D,YAAa,gDAAiDkG,KAAM,CAAE9J,KAAM,QACpH,CAAE+H,MAAO,cAAe7H,MAAO,cAAe0D,YAAa,yBAA0BkG,KAAM,CAAE9J,KAAM,QACnG,CAAE+H,MAAO,UAAW7H,MAAO,UAAW0D,YAAa,2BAA4BkG,KAAM,CAAE9J,KAAM,QAC7F,CAAE+H,MAAO,WAAY7H,MAAO,WAAY0D,YAAa,2BAA4BkG,KAAM,CAAE9J,KAAM,QAC/F,CAAE+H,MAAO,WAAY7H,MAAO,WAAY0D,YAAa,yBAA0BkG,KAAM,CAAE9J,KAAM,QAC7F,CAAE+H,MAAO,OAAQ7H,MAAO,OAAQ0D,YAAa,iCAAkCkG,KAAM,CAAE9J,KAAM,QAC7F,CAAE+H,MAAO,SAAU7H,MAAO,SAAU0D,YAAa,kCAAmCkG,KAAM,CAAE9J,KAAM,QAClG,CAAE+H,MAAO,SAAU7H,MAAO,SAAU0D,YAAa,uBAAwBkG,KAAM,CAAE9J,KAAM,QACvF,CAAE+H,MAAO,cAAe7H,MAAO,cAAe0D,YAAa,0CAA2CkG,KAAM,CAAE9J,KAAM,QACpH,CAAE+H,MAAO,UAAW7H,MAAO,UAAW0D,YAAa,qBAAsBkG,KAAM,CAAE9J,KAAM,QACvF,CAAE+H,MAAO,OAAQ7H,MAAO,OAAQ0D,YAAa,wBAAyBkG,KAAM,CAAE9J,KAAM,QACpF,CAAE+H,MAAO,QAAS7H,MAAO,QAAS0D,YAAa,cAAekG,KAAM,CAAE9J,KAAM,QAC5E,CAAE+H,MAAO,eAAgB7H,MAAO,eAAgB0D,YAAa,qCAAsCkG,KAAM,CAAE9J,KAAM,QACjH,CAAE+H,MAAO,SAAU7H,MAAO,SAAU0D,YAAa,+BAAgCkG,KAAM,CAAE9J,KAAM,QAC/F,CAAE+H,MAAO,OAAQ7H,MAAO,OAAQ0D,YAAa,6BAA8BkG,KAAM,CAAE9J,KAAM,QACzF,CAAE+H,MAAO,kBAAmB7H,MAAO,kBAAmB0D,YAAa,0CAA2CkG,KAAM,CAAE9J,KAAM,QAC5H,CAAE+H,MAAO,eAAgB7H,MAAO,eAAgB0D,YAAa,qCAAsCkG,KAAM,CAAE9J,KAAM,QACjH,CAAE+H,MAAO,SAAU7H,MAAO,SAAU0D,YAAa,+BAAgCkG,KAAM,CAAE9J,KAAM,SAGnG,MAAM+J,gBAAgB1M,EAAAA,MAClB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,+BAElB,EAGJ,MAAMuM,oBAAoBrM,EAAAA,WACtB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYkM,QACZtM,SAAU,iCACPG,GAEX,EAGC,MAACqM,EAAe,CACjBpK,OAAQ,CACJC,MAAO,iBACPhC,KAAM,KACNiC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,OACPM,UAAU,EACVF,YAAa,qBACbwH,KAAM,IAEV,CACI7H,KAAM,WACND,KAAM,OACNE,MAAO,WACPM,UAAU,EACVF,YAAa,+BACbwH,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,SACNE,MAAO,WACP6H,MAAO,GACPvH,UAAU,EACVsH,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,SACNE,MAAO,cACP6H,MAAO,EACPnK,QAAS8L,EACT5B,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP6H,MAAO,EACPnK,QAAS6L,EACT3B,KAAM,IAEV,CACI7H,KAAM,iBACND,KAAM,SACNE,MAAO,iBACP6H,MAAO,GACPzH,YAAa,2BACbwH,KAAM,IAEV,CACI7H,KAAM,UACND,KAAM,OACNE,MAAO,UACPI,YAAa,8CACbwH,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP6H,OAAO,EACPD,KAAM,GAEV,CACI7H,KAAM,qBACND,KAAM,SACNE,MAAO,qBACP6H,OAAO,EACPD,KAAM,KAIlBjH,KAAM,CACFf,MAAO,eACPhC,KAAM,KACNiC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,OACPM,UAAU,EACVF,YAAa,qBACbwH,KAAM,IAEV,CACI7H,KAAM,WACND,KAAM,OACNE,MAAO,WACPM,UAAU,EACVF,YAAa,+BACbwH,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,SACNE,MAAO,WACPM,UAAU,EACVsH,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,SACNE,MAAO,cACPtC,QAAS8L,EACT5B,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACPtC,QAAS6L,EACT3B,KAAM,IAEV,CACI7H,KAAM,iBACND,KAAM,SACNE,MAAO,iBACPI,YAAa,2BACb1C,QAAW,CACT,CAACmK,MAAS,EAAG7H,MAAS,mCACtB,CAAC6H,MAAS,EAAG7H,MAAS,aACtB,CAAC6H,MAAS,GAAI7H,MAAS,cACvB,CAAC6H,MAAS,GAAI7H,MAAS,cACvB,CAAC6H,MAAS,GAAI7H,MAAS,cACvB,CAAC6H,MAAS,GAAI7H,MAAS,UACvB,CAAC6H,MAAS,IAAK7H,MAAS,WACxB,CAAC6H,MAAS,IAAK7H,MAAS,WACxB,CAAC6H,MAAS,IAAK7H,MAAS,YACxB,CAAC6H,MAAS,KAAM7H,MAAS,SACzB,CAAC6H,MAAS,KAAM7H,MAAS,8BAE3B4H,KAAM,IAEV,CACI7H,KAAM,UACND,KAAM,OACNE,MAAO,UACPI,YAAa,8CACbwH,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP6H,OAAO,EACPD,KAAM,GAEV,CACI7H,KAAM,qBACND,KAAM,SACNE,MAAO,qBACP6H,OAAO,EACPD,KAAM,MAStB,MAAMoC,aAAa7M,EAAAA,MACf,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,oCAElB,EAGJ,MAAM0M,iBAAiBxM,EAAAA,WACnB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYqM,KACZzM,SAAU,sCACPG,GAEX,EAGC,MAACwM,EAAY,CACdvK,OAAQ,CACJC,MAAO,cACPC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,OACPM,UAAU,EACVF,YAAa,kBACbwH,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,QACNE,MAAO,aACPM,UAAU,EACVF,YAAa,gCACb1C,QAASiM,EACTQ,aAAa,EACbC,iBAAiB,EACjB1J,KAAM,oDACNkH,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,SACNE,MAAO,aACPM,UAAU,EACV5C,QAAS+L,EACT7B,KAAM,GAEV,CACI7H,KAAM,aACND,KAAM,SACNE,MAAO,aACPM,UAAU,EACV5C,QAASgM,EACT7B,MAAO,MACPD,KAAM,GAEV,CACI7H,KAAM,QACND,KAAM,OACNE,MAAO,QACPM,UAAU,EACVF,YAAa,yBACbwH,KAAM,MAIlBjH,KAAM,CACFf,MAAO,YACPC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,OACPM,UAAU,EACVF,YAAa,kBACbwH,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,QACNE,MAAO,aACPM,UAAU,EACVF,YAAa,gCACb1C,QAASiM,EACTQ,aAAa,EACbC,iBAAiB,EACjB1J,KAAM,oDACNkH,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,SACNE,MAAO,aACPM,UAAU,EACV5C,QAAS+L,EACT7B,KAAM,GAEV,CACI7H,KAAM,aACND,KAAM,SACNE,MAAO,aACPM,UAAU,EACV5C,QAASgM,EACT9B,KAAM,GAEV,CACI7H,KAAM,QACND,KAAM,OACNE,MAAO,QACPM,UAAU,EACVF,YAAa,yBACbwH,KAAM,OAOtBiC,QAAQQ,UAAYN,EAAapJ,KACjCkJ,QAAQS,SAAWP,EAAapK,OAChCkK,QAAQU,YAAcR,EAAapK,OACnCkK,QAAQN,gBAAkBA,EAC1BM,QAAQL,eAAiBA,EAEzBQ,KAAKK,UAAYH,EAAUvJ,KAC3BqJ,KAAKM,SAAWJ,EAAUvK,OAC1BqK,KAAKO,YAAcL,EAAUvK,OAC7BqK,KAAKP,kBAAoBA,EACzBO,KAAKN,iBAAmBA,EChrBxB,MAAMc,YAAYrN,EAAAA,MACd,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,iBAElB,CAGA,YAAMwJ,GACF,MAAM1B,QAAiBtH,KAAK6I,KAAK,CAAE6D,gBAAgB,IAKnD,OAJIpF,EAASnH,SAAWmH,EAAShI,KAAKc,QAElCJ,KAAKwH,IAAI,oBAAoB,GAE1BF,CACX,CAEA,WAAMqF,CAAMC,EAAQ,MAChB,MAAMtN,EAAOsN,EACT,CAAEC,cAAe,CAAEF,OAAO,EAAMC,UAChC,CAAEC,eAAe,GAEfvF,QAAiBtH,KAAK6I,KAAKvJ,GACjC,OAAIgI,EAASnH,SAAWmH,EAAShI,KAAKc,QAAUkH,EAAShI,KAAKwN,WAEnD,IACAxF,EACHyF,SAAUzF,EAAShI,KAAKwN,WACxBE,cAAe1F,EAAShI,KAAK2N,iBAG9B3F,CACX,CAEA,uBAAM4F,GACF,MAAM5F,QAAiBtH,KAAK6I,KAAK,CAAEsE,YAAY,IAK/C,OAJI7F,EAASnH,SAAWmH,EAAShI,KAAKc,QAElCJ,KAAKwH,IAAIF,EAAShI,KAAKA,MAEpBgI,CACX,CAEA,cAAM8F,CAASC,EAAa,IACxB,MAAMC,EAAc,CAChBC,YAAa,CACTpG,QAASkG,KACNA,IAIL/F,QAAiBtH,KAAK6I,KAAKyE,GACjC,OAAIhG,EAASnH,SAAWmH,EAAShI,KAAKc,QAAUkH,EAAShI,KAAKkO,OACnD,IACAlG,EACHyF,SAAUzF,EAAShI,KAAKkO,OACxBC,cAAenG,EAAShI,KAAKoO,iBAG9BpG,CACX,CAGA,QAAAqG,GACI,MAAMvN,EAASJ,KAAKsK,IAAI,UACxB,MAAO,CAAC,UAAW,WAAWsD,SAASxN,EAC3C,CAEA,UAAAyN,GACI,MAAMzN,EAASJ,KAAKsK,IAAI,UACxB,MAAO,CAAC,YAAa,SAAU,WAAY,WAAWsD,SAASxN,EACnE,CAEA,QAAA0N,GACI,MAAM1N,EAASJ,KAAKsK,IAAI,UACxB,MAAO,CAAC,SAAU,WAAY,WAAWsD,SAASxN,KAAwC,IAA7BJ,KAAKsK,IAAI,eAC1E,CAEA,SAAAyD,GACI,MAAM3N,EAASJ,KAAKsK,IAAI,UACxB,MAAO,CAAC,UAAW,WAAWsD,SAASxN,KAAYJ,KAAKsK,IAAI,mBAChE,CAGA,mBAAA0D,GAUI,MARgB,CACZC,QAAW,aACXC,QAAW,aACXhK,UAAa,UACbiK,OAAU,YACVC,SAAY,eACZC,QAAW,cAPArO,KAAKsK,IAAI,YASE,cAC9B,CAGA,aAAAgE,GAUI,MARc,CACVL,QAAW,eACXC,QAAW,kBACXhK,UAAa,kBACbiK,OAAU,eACVC,SAAY,cACZC,QAAW,YAPArO,KAAKsK,IAAI,YASA,oBAC5B,CAGA,SAAAiE,GACI,OAAOvO,KAAKsK,IAAI,kBAAoB,EACxC,CAGA,oBAAAkE,GACI,MAAMC,EAAWzO,KAAKsK,IAAI,eAC1B,OAAKmE,GAAyB,IAAbA,EAEbA,EAAW,IAAa,GAAGA,MAC3BA,EAAW,IAAc,IAAIA,EAAW,KAAMC,QAAQ,MACtDD,EAAW,KAAgB,IAAIA,EAAW,KAAOC,QAAQ,MACtD,IAAID,EAAW,MAASC,QAAQ,MALC,KAM5C,CAGA,gBAAAC,GACI,OAAO3O,KAAKsK,IAAI,iBACpB,CAGA,UAAAsE,GACI,MAAMC,EAAY7O,KAAKsK,IAAI,cAC3B,QAAKuE,GACE,IAAIC,KAAKD,sBAAiBC,IACrC,CAGA,WAAAC,GACI,OAAO/O,KAAKsK,IAAI,YACpB,CAGA,UAAA0E,GACI,OAAOhP,KAAKsK,IAAI,YAAc,CAAA,CAClC,CAGA,WAAA2E,GACI,OAAOjP,KAAKsK,IAAI,aAAe,CAAA,CACnC,EAGJ,MAAM4E,gBAAgBxP,EAAAA,WAClB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAY6M,IACZjN,SAAU,mBACPG,GAEX,CAGA,mBAAMwP,CAAc/O,EAAQM,EAAS,IACjC,OAAOV,KAAKoP,MAAM,CAAEhP,YAAWM,GACnC,CAGA,oBAAM2O,CAAeC,EAAS5O,EAAS,IACnC,OAAOV,KAAKoP,MAAM,CAAEE,aAAY5O,GACpC,CAGA,kBAAM6O,CAAa7O,EAAS,IACxB,OAAOV,KAAKmP,cAAc,UAAWzO,EACzC,CAGA,kBAAM8O,CAAa9O,EAAS,IACxB,OAAOV,KAAKmP,cAAc,UAAWzO,EACzC,CAGA,oBAAM+O,CAAe/O,EAAS,IAC1B,OAAOV,KAAKmP,cAAc,YAAazO,EAC3C,CAGA,iBAAMgP,CAAYhP,EAAS,IACvB,OAAOV,KAAKmP,cAAc,SAAUzO,EACxC,CAGA,oBAAMiP,CAAejP,EAAS,IAC1B,OAAOV,KAAKoP,MAAM,CACdQ,WAAW,KACRlP,GAEX,EA+GJ+L,IAAIoD,QAAUpO,eAAeqO,GACzB,aAAatP,EAAAA,KAAKC,KAAK,oBAAqBqP,EAChD,EAGArD,IAAInH,SAAW7D,iBACX,MAAM6F,QAAiB9G,OAAKQ,IAAI,mBAChC,OAAOsG,EAASnH,QAAUmH,EAAShI,KAAO,IAC9C,EAGAmN,IAAIsD,UAAYtO,eAAe6N,EAAU,MACrC,MAAM9P,EAAW8P,EAAU,oBAAoBA,IAAY,mBACrDhI,EAAW9G,EAAAA,KAAKQ,IAAIxB,GAC1B,OAAO8H,EAASnH,QAAUmH,EAAShI,KAAO,IAC9C,EAGAmN,IAAIuD,KAAOvO,iBACP,aAAajB,EAAAA,KAAKC,KAAK,iBAAkB,CAAA,EAC7C,EAGAgM,IAAIwD,MAAQxO,iBACR,aAAajB,EAAAA,KAAKC,KAAK,kBAAmB,CAAA,EAC9C,EAEAgM,IAAIyD,WAAazO,eAAe6N,EAAU,MACtC,MAAMnI,EAAUmI,EAAU,CAAEA,WAAY,CAAA,EACxC,aAAa9O,EAAAA,KAAKC,KAAK,gCAAiC0G,EAC5D,EAEAsF,IAAI0D,aAAe1O,eAAe6N,GAC9B,aAAa9O,EAAAA,KAAKC,KAAK,gCAAiC,CAAE6O,UAASc,QAAQ,OAC/E,EAEA3D,IAAI4D,eAAiB5O,iBACjB,aAAajB,EAAAA,KAAKC,KAAK,sCAC3B,EAEAgM,IAAI6D,UAAY7O,eAAe8O,GAC3B,aAAa/P,EAAAA,KAAKC,KAAK,0BAA2B,CAAE8P,YACxD,EAGA,MAAMC,eAAepR,EAAAA,MACjB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,kBAElB,EAGJ,MAAMiR,mBAAmB/Q,EAAAA,WACrB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFC,SAAU,iBACV2B,MAAOqP,UACJ7Q,GAEX,EAIJ,MAAM+Q,iBAAiBtR,EAAAA,MACnB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,mBAElB,EAGJ,MAAMmR,qBAAqBjR,EAAAA,WACvB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFC,SAAU,kBACV2B,MAAOuP,YACJ/Q,GAEX,EAGJ,MAAMiR,wBAAwBxR,EAAAA,MAC1B,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,kBACViL,YAAY,GAEpB,EChZJ,MAAMoG,kBAAkBzR,EAAAA,MACpB,WAAAC,CAAYC,EAAO,IAEXA,EAAKwR,YAAcxR,EAAKW,KACxBX,EAAKW,GAAKX,EAAKwR,WAGnBvR,MAAMD,EAAM,CACRE,SAAU,oBACVuR,YAAa,aAErB,CAGA,UAAMC,CAAKrI,EAAU,GACjB,MAAMsI,EAAMjR,KAAKkR,SACjB,IAAKD,IAAQA,EAAIzQ,KACb,MAAM,IAAIyF,MAAM,oCAGpB,MAAMqB,QAAiB2J,EAAIzQ,KAAKC,KAAK,yBAA0B,CAC3DqQ,UAAW9Q,KAAKsK,IAAI,aACpB3B,YASJ,OANIrB,EAASnH,SAAWmH,EAAShI,KAAKc,SAElCJ,KAAKwH,IAAI,iCAAA,IAAsBsH,MAAOqC,eACtCnR,KAAKwH,IAAI,cAAeF,EAAShI,KAAK8R,aAAe,YAGlD9J,CACX,CAGA,cAAM+J,CAASC,GAAW,GACtB,MAAML,EAAMjR,KAAKkR,SACjB,IAAKD,IAAQA,EAAIzQ,KACb,MAAM,IAAIyF,MAAM,oCAGpB,MAAMqB,QAAiB2J,EAAIzQ,KAAKC,KAAK,6BAA8B,CAC/DqQ,UAAW9Q,KAAKsK,IAAI,aACpBgH,aAQJ,OALIhK,EAASnH,SAAWmH,EAAShI,KAAKc,QAElCJ,KAAKwH,IAAI,SAAS,GAGfF,CACX,CAGA,WAAAiK,GACI,OAAOvR,KAAKsK,IAAI,aAAe,EACnC,CAGA,mBAAA0D,GAEI,OADchO,KAAKsK,IAAI,SACR,aAAe,WAClC,CAGA,aAAAgE,GAEI,OADctO,KAAKsK,IAAI,SACR,uBAAyB,mBAC5C,CAGA,QAAAqD,GACI,OAA6B,IAAtB3N,KAAKsK,IAAI,QACpB,CAGA,SAAAkH,GACI,IAAKxR,KAAK2N,WAAY,OAAO,EAE7B,MAAM8D,EAAgBzR,KAAKsK,IAAI,kBAC/B,QAAKmH,IAGiB3C,KAAK4C,MAAQ,IAAI5C,KAAK2C,GAAeE,WAAa,IAClD,GAC1B,CAGA,wBAAAC,GACI,MAAMH,EAAgBzR,KAAKsK,IAAI,kBAC/B,IAAKmH,EAAe,MAAO,QAE3B,MAAMI,GAAgB/C,KAAK4C,MAAQ,IAAI5C,KAAK2C,GAAeE,WAAa,IAExE,OAAIE,EAAe,GAAW,GAAG5J,KAAKC,MAAM2J,UACxCA,EAAe,KAAa,GAAG5J,KAAKC,MAAM2J,EAAe,WACtD,GAAG5J,KAAKC,MAAM2J,EAAe,YACxC,CAGA,cAAAC,GACI,MAEMlO,GAFY5D,KAAKsK,IAAI,mBAAqB,IACjCtK,KAAKsK,IAAI,gBAAkB,GAG1C,OAAO1G,EAAQ,GAAK,IAAMqE,KAAK8J,IAAY,GAARnO,EAAY,IACnD,CAGA,kBAAAoO,GACI,MAAMC,EAAUjS,KAAKsK,IAAI,WACzB,IAAK2H,EAAS,MAAO,UAErB,MAAMC,EAAY,IAAIpD,KAAKmD,GAErBE,qBADUrD,KACKoD,EACfE,EAAcnK,KAAKoK,MAAMF,EAAS,KAExC,OAAIC,EAAc,GAAW,GAAGA,KAC5BA,EAAc,KAAa,GAAGnK,KAAKoK,MAAMD,EAAc,OACvDA,EAAc,MAAc,GAAGnK,KAAKoK,MAAMD,EAAc,SACrD,GAAGnK,KAAKoK,MAAMD,EAAc,SACvC,CAGA,aAAAE,GACI,MAAMC,EAAYvS,KAAKsK,IAAI,mBAAqB,EAC1C6D,EAASnO,KAAKsK,IAAI,gBAAkB,EAC1C,MAAO,CACHiI,YACApE,SACAvK,MAAO2O,EAAYpE,EACnBqE,MAAOxS,KAAKsK,IAAI,SAChBmI,YAAazS,KAAK8R,iBAE1B,CAGA,cAAAY,GACI,MAAMC,EAAW3S,KAAKsK,IAAI,aAC1B,IAAKqI,EAAU,MAAO,iBAGtB,MAAMC,EAAQD,EAASE,MAAM,KAC7B,OAAOD,EAAME,OAAS,EAAIF,EAAM,GAAKD,CACzC,CAGA,UAAAI,GACI,OAA6B,IAAtB/S,KAAKsK,IAAI,QACpB,EAGJ,MAAM0I,sBAAsBtT,EAAAA,WACxB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYiR,UACZrR,SAAU,uBACPG,GAEX,CAGA,SAAAsT,GACI,OAAOjT,KAAKkT,MAAMC,GAAUA,EAAOxF,WACvC,CAGA,YAAAyF,CAAa9D,GACT,OAAOtP,KAAKkT,MAAMC,GAAUA,EAAO5B,cAAc3D,SAAS0B,GAC9D,CAGA,UAAA+D,GACI,OAAOrT,KAAKkT,MAAMC,GAAUA,EAAO3B,YACvC,CAGA,iBAAA8B,GACI,OAAOtT,KAAKuT,OAAOC,OAAO,CAAC5P,EAAOuP,IACvBvP,GAASuP,EAAO7I,IAAI,mBAAqB,GACjD,EACP,CAGA,cAAAmJ,GACI,OAAOzT,KAAKuT,OAAOC,OAAO,CAAC5P,EAAOuP,IACvBvP,GAASuP,EAAO7I,IAAI,gBAAkB,GAC9C,EACP,CAGA,eAAAoJ,GACI,GAA2B,IAAvB1T,KAAKuT,OAAOT,OAAc,OAAO,EACrC,MAAMa,EAAe3T,KAAKuT,OAAOK,UAAiBT,EAAO7I,IAAI,UAAUwI,OACvE,OAAO7K,KAAKC,MAAOyL,EAAe3T,KAAKuT,OAAOT,OAAU,IAC5D,CAGA,cAAAe,GACI,MAAMC,qBAAeC,IAIrB,OAHA/T,KAAKuT,OAAOS,QAAQb,IAChBA,EAAO5B,cAAcyC,WAAmBF,EAASG,IAAI3E,MAElD4E,MAAMC,KAAKL,GAAUM,MAChC,EAIJvD,UAAUG,KAAOvP,eAAekR,EAAUhK,EAAU,GAChD,MAAMsI,EAAwB,oBAAXoD,QAA0BA,OAAOpD,IACpD,IAAKA,IAAQA,EAAIzQ,KACb,MAAM,IAAIyF,MAAM,oCAGpB,aAAagL,EAAIzQ,KAAKC,KAAK,yBAA0B,CACjDqQ,UAAW6B,EACXhK,WAER,EAEAkI,UAAUQ,SAAW5P,eAAekR,EAAUrB,GAAW,GACrD,MAAML,EAAwB,oBAAXoD,QAA0BA,OAAOpD,IACpD,IAAKA,IAAQA,EAAIzQ,KACb,MAAM,IAAIyF,MAAM,oCAGpB,aAAagL,EAAIzQ,KAAKC,KAAK,6BAA8B,CACrDqQ,UAAW6B,EACXrB,YAER,EAEAT,UAAUyD,UAAY7S,eAAe8S,EAASjV,EAAO,CAAA,EAAIqJ,EAAU,GAC/D,MAAMsI,EAAwB,oBAAXoD,QAA0BA,OAAOpD,IACpD,IAAKA,IAAQA,EAAIzQ,KACb,MAAM,IAAIyF,MAAM,oCAGpB,aAAagL,EAAIzQ,KAAKC,KAAK,8BAA+B,CACtD8T,UACAjV,OACAqJ,WAER,EAGAkI,UAAU2D,gBAAkB/S,eAAekH,EAAU,GACjD,OAAOkI,UAAUyD,UAAU,SAAU,CAAA,EAAI3L,EAC7C,EAEAkI,UAAU4D,kBAAoBhT,eAAekH,EAAU,GACnD,OAAOkI,UAAUyD,UAAU,WAAY,CAAA,EAAI3L,EAC/C,EAEAkI,UAAU6D,eAAiBjT,eAAekH,EAAU,GAChD,OAAOkI,UAAUyD,UAAU,QAAS,CAAA,EAAI3L,EAC5C,EAEAkI,UAAU8D,gBAAkBlT,eAAekH,EAAU,GACjD,OAAOkI,UAAUyD,UAAU,SAAU,CAAA,EAAI3L,EAC7C,EAEAkI,UAAU+D,gBAAkBnT,eAAekH,EAAU,GACjD,OAAOkI,UAAUyD,UAAU,SAAU,CAAA,EAAI3L,EAC7C,EC3QA,MAAMkM,YAAYzV,EAAAA,MACd,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,aAElB,EAMJ,MAAMsV,gBAAgBpV,EAAAA,WAClB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYiV,IACZrV,SAAU,YACVK,KAAM,MACHF,GAEX,ECnBJ,MAAMoV,eAAe3V,EAAAA,MACjB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,qBAElB,CAEA,aAAAwV,CAAcC,GACV,GAAIf,MAAMgB,QAAQD,GACd,OAAOA,EAAWE,KAAKC,GAAKpV,KAAKgV,cAAcI,IAEnD,MAAMC,EAAcrV,KAAKsK,IAAI,eAC7B,QAAK+K,GAG6B,GAA3BA,EAAYJ,EACvB,CAEC,mBAAMK,CAAcC,GAChB,aAAavV,KAAKoP,MAAM,CAAE9O,IAAK,cAAciV,YAClD,EAMJ,MAAMC,mBAAmB9V,EAAAA,WACrB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYmV,OACZvV,SAAU,oBACVK,KAAM,MACHF,GAEX,EAMC,MAAC8V,EAAc,CAEhB7S,KAAM,CACFf,MAAO,kBACPC,OAAQ,CACJ,CACIE,KAAM,oBACND,KAAM,OACNE,MAAO,eACPI,YAAa,sBAEjB,CACIL,KAAM,gBACND,KAAM,OACNE,MAAO,OACPI,YAAa,cAEjB,CACIL,KAAM,YACND,KAAM,SACNE,MAAO,aACPQ,QAAS,OAOzBsS,OAAOzI,UAAYmJ,EAAY7S,KAC/BmS,OAAOxI,SAAWkJ,EAAY7T,OCzE9B,MAAM8T,0BAA0BtW,EAAAA,MAC5B,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,2BACVmW,OAAQ,WAEhB,EAGJ,MAAMC,8BAA8BlW,EAAAA,WAChC,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAY8V,kBACZlW,SAAU,8BACPG,GAEX,ECZJ,MAAMkW,mBAAmBzW,EAAAA,MACrB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CAAEE,SAAU,6BAC5B,EAGJ,MAAMsW,uBAAuBpW,EAAAA,WACzB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CAAEK,WAAYiW,WAAYrW,SAAU,+BAAgCG,GAC9E,EAMJ,MAAMoW,qBAAqB3W,EAAAA,MACvB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CAAEE,SAAU,uCAC5B,EAGJ,MAAMwW,yBAAyBtW,EAAAA,WAC3B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CAAEK,WAAYmW,aAAcvW,SAAU,yCAA0CG,GAC1F,EAMJ,MAAMsW,mBAAmB7W,EAAAA,MACrB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CAAEE,SAAU,oCAC5B,EAGJ,MAAM0W,uBAAuBxW,EAAAA,WACzB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CAAEK,WAAYqW,WAAYzW,SAAU,sCAAuCG,GACrF,EAMJ,MAAMwW,qBAAqB/W,EAAAA,MACvB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CAAEE,SAAU,wCAC5B,EAGJ,MAAM4W,yBAAyB1W,EAAAA,WAC3B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CAAEK,WAAYuW,aAAc3W,SAAU,0CAA2CG,GAC3F,EAMC,MAAC0W,EAAkB,CACpBzU,OAAQ,CACJC,MAAO,4BACPC,OAAQ,CACJ,CAAEE,KAAM,OAAQC,MAAO,OAAQM,UAAU,GACzC,CAAER,KAAM,aAAcC,KAAM,QAASC,MAAO,mBAAoBvC,WAAYwK,EAAAA,UAAWhI,WAAY,OAAQC,WAAY,MACvH,CAAEH,KAAM,sBAAuBC,MAAO,kBAAmBF,KAAM,WAAYuU,KAAM,MAGzF1T,KAAM,CACFf,MAAO,0BACPC,OAAQ,CACJ,CAAEE,KAAM,OAAQC,MAAO,OAAQM,UAAU,GACzC,CAAER,KAAM,aAAcC,KAAM,QAASC,MAAO,mBAAoBvC,WAAYwK,EAAAA,UAAWhI,WAAY,OAAQC,WAAY,MACvH,CAAEH,KAAM,sBAAuBC,MAAO,kBAAmBF,KAAM,WAAYuU,KAAM,IACjF,CAAEtU,KAAM,YAAaC,MAAO,YAAaF,KAAM,SAAU+H,OAAO,MAKtEyM,EAAoB,CACtB3T,KAAM,CACFf,MAAO,qBACPC,OAAQ,CACJ,CAAEE,KAAM,OAAQC,MAAO,OAAQM,UAAU,GACzC,CAAEP,KAAM,WAAYC,MAAO,WAAYM,UAAU,GACjD,CACIR,KAAM,aACNC,KAAM,QACNC,MAAO,mBACPvC,WAAYwK,EAAAA,UACZhI,WAAY,OACZC,WAAY,KACZqU,cAAe,CAAEC,WAAW,IAEhC,CAAEzU,KAAM,iBAAkBC,MAAO,iBAAkBM,UAAU,GAC7D,CAAEP,KAAM,gBAAiBC,MAAO,gBAAiBF,KAAM,WAAYQ,UAAU,GAC7E,CAAEP,KAAM,aAAcC,MAAO,cAC7B,CAAED,KAAM,WAAYC,MAAO,WAAYF,KAAM,SAAUpC,QAAS,CAAC,OAAQ,WACzE,CAAEqC,KAAM,YAAaC,MAAO,YAAaF,KAAM,OAAQY,KAAM,eAC7D,CAAEX,KAAM,YAAaC,MAAO,YAAaF,KAAM,aAK3DsU,EAAgBzU,OAASyU,EAAgBzT,KACzC2T,EAAkB3U,OAAS2U,EAAkB3T,KC1G7C,MAAM8T,EACY,CACV7U,MAAO,gBACPhC,KAAM,KACNiC,OAAQ,CACJ,CAAEE,KAAM,aAAcC,MAAO,aAAcF,KAAM,OAAQQ,UAAU,EAAMoU,UAAU,EAAM9M,KAAM,GAC/F,CAAE7H,KAAM,SAAUC,MAAO,SAAUF,KAAM,OAAQ8H,KAAM,GACvD,CAAE7H,KAAM,eAAgBC,MAAO,UAAWF,KAAM,OAAQ8H,KAAM,GAC9D,CAAE7H,KAAM,eAAgBC,MAAO,eAAgBF,KAAM,OAAQ8H,KAAM,GACnE,CAAE7H,KAAM,SAAUC,MAAO,SAAUF,KAAM,OAAQ8H,KAAM,GACvD,CAAE7H,KAAM,OAAQC,MAAO,OAAQF,KAAM,OAAQ8H,KAAM,GACnD,CAAE7H,KAAM,cAAeC,MAAO,cAAeF,KAAM,OAAQ8H,KAAM,GACjE,CAAE7H,KAAM,WAAYC,MAAO,WAAYF,KAAM,OAAQ8H,KAAM,GAC3D,CAAE7H,KAAM,WAAYC,MAAO,WAAYF,KAAM,SAAU6U,KAAM,MAAO/M,KAAM,GAC1E,CAAE7H,KAAM,YAAaC,MAAO,YAAaF,KAAM,SAAU6U,KAAM,MAAO/M,KAAM,KAgDxF,MAAMgN,qBAAqBzX,EAAAA,MACvB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,qBAElB,CAEA,mBAAasX,CAAOC,GAChB,MAAM5V,EAAQ,IAAI0V,aACZG,QAAa7V,EAAMX,KAAKQ,IAAI,2BAA4B,CAAE+V,OAChE,OAAIC,EAAK7W,SAAW6W,EAAK1X,MAAQ0X,EAAK1X,KAAKA,KAChC,IAAIuX,aAAaG,EAAK1X,KAAKA,MAE/B,IACX,EAIJuX,aAAavK,UAAYoK,EACzBG,aAAaI,mBAAqBP,EAClCG,aAAaK,mBAjEK,CACVrV,MAAO,gBACPhC,KAAM,KACNiC,OAAQ,CACJ,CACIE,KAAM,eACNC,MAAO,eACPF,KAAM,SACN8H,KAAM,GACNlK,QAAS,CACL,CAAEmK,MAAO,GAAI7H,MAAO,QACpB,CAAE6H,MAAO,MAAO7H,MAAO,OACvB,CAAE6H,MAAO,SAAU7H,MAAO,UAC1B,CAAE6H,MAAO,OAAQ7H,MAAO,QACxB,CAAE6H,MAAO,WAAY7H,MAAO,cAGpC,CAAED,KAAM,YAAaC,MAAO,SAAUF,KAAM,SAAU8H,KAAM,GAC5D,CAAE7H,KAAM,gBAAiBC,MAAO,aAAcF,KAAM,SAAU8H,KAAM,GACpE,CAAE7H,KAAM,oBAAqBC,MAAO,iBAAkBF,KAAM,SAAU8H,KAAM,GAC5E,CAAE7H,KAAM,kBAAmBC,MAAO,eAAgBF,KAAM,SAAU8H,KAAM,GACxE,CAAE7H,KAAM,aAAcC,MAAO,aAAcF,KAAM,SAAU8H,KAAM,GACjE,CAAE7H,KAAM,SAAUC,MAAO,gBAAiBF,KAAM,SAAU8H,KAAM,GAChE,CAAE7H,KAAM,SAAUC,MAAO,MAAOF,KAAM,SAAU8H,KAAM,GACtD,CAAE7H,KAAM,WAAYC,MAAO,QAASF,KAAM,SAAU8H,KAAM,GAC1D,CAAE7H,KAAM,WAAYC,MAAO,iBAAkBF,KAAM,SAAU8H,KAAM,GACnE,CAAE7H,KAAM,gBAAiBC,MAAO,aAAcF,KAAM,SAAU8H,KAAM,KAwChFgN,aAAaM,kBArCI,CACTtV,MAAO,eACPhC,KAAM,KACNiC,OAAQ,CACJ,CAAEE,KAAM,MAAOC,MAAO,MAAOF,KAAM,OAAQ8H,KAAM,GACjD,CAAE7H,KAAM,UAAWC,MAAO,mBAAoBF,KAAM,OAAQ8H,KAAM,GAClE,CAAE7H,KAAM,MAAOC,MAAO,MAAOF,KAAM,OAAQ8H,KAAM,IACjD,CAAE7H,KAAM,kBAAmBC,MAAO,kBAAmBF,KAAM,OAAQ8H,KAAM,GACzE,CAAE7H,KAAM,WAAYC,MAAO,WAAYF,KAAM,OAAQ8H,KAAM,GAC3D,CAAE7H,KAAM,YAAaC,MAAO,oBAAqBF,KAAM,SAAU8H,KAAM,GACvE,CAAE7H,KAAM,iBAAkBC,MAAO,iBAAkBF,KAAM,OAAQ8H,KAAM,GACvE,CAAE7H,KAAM,YAAaC,MAAO,YAAaF,KAAM,WAAY8H,KAAM,MA4B7E,MAAMuN,yBAAyB1X,EAAAA,WAC3B,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYiX,aACZrX,SAAU,uBACPG,GAEX,EC3FJ,MAAM0X,eAAejY,EAAAA,MACjB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,wBAElB,EAGJ,MAAM8X,mBAAmB5X,EAAAA,WACrB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAYyX,OACZ7X,SAAU,0BACPG,GAEX,EAIC,MAAC4X,EAAmB,CACrBC,OAAU,SACVC,IAAO,MACPC,QAAW,kBACXC,SAAY,WACZC,SAAY,oBACZC,YAAe,cACfC,SAAY,WACZC,UAAa,YACbC,GAAM,qBAIJC,EAA0BC,OAAOC,QAAQZ,GAAkBa,IAAI,EAAEC,EAAKpW,MAAK,CAC7E6H,MAAOuO,EACPpW,WAIEqW,EAAc,CAChB1W,OAAQ,CACJC,MAAO,gBACPC,OAAQ,CACJ,CACIE,KAAM,QAASD,KAAM,OACrBE,MAAO,QAASM,UAAU,EAAMsH,KAAM,IAE1C,CAAE7H,KAAM,cAAeD,KAAM,WAAYE,MAAO,cAAeM,UAAU,EAAOsH,KAAM,IACtF,CACI7H,KAAM,WAAYD,KAAM,SAAUE,MAAO,WACzCtC,QAASsY,EAAyBpO,KAAM,GAAIC,MAAO,UACvD,CACI9H,KAAM,WAAYD,KAAM,SACxBE,MAAO,WAAY6H,MAAO,EAAGD,KAAM,GAEvC,CACI7H,KAAM,SAAUD,KAAM,SAAUE,MAAO,SACvCtC,QAAS,CAAC,MAAO,OAAQ,SAAU,WAAY,KAAM,WACrDkK,KAAM,EAAGC,MAAO,OAEpB,CACI/H,KAAM,aAAcC,KAAM,WAC1BC,MAAO,WAAYvC,WAAYyK,EAAAA,SAC/BjI,WAAY,eACZC,WAAY,KACZ0H,KAAM,IAEV,CACI9H,KAAM,aAAcC,KAAM,WAC1BC,MAAO,WAAYvC,WAAYuL,aAC/B/I,WAAY,QACZC,WAAY,KACZ0H,KAAM,MAIlBjH,KAAM,CACFf,MAAO,cACPC,OAAQ,CACJ,CAAEE,KAAM,QAASD,KAAM,OAAQE,MAAO,QAASM,UAAU,EAAMsH,KAAM,IACrE,CAAE7H,KAAM,cAAeD,KAAM,WAAYE,MAAO,cAAeM,UAAU,EAAOsH,KAAM,IACtF,CAAE7H,KAAM,WAAYD,KAAM,SAAUE,MAAO,WAAYtC,QAASsY,EAAyBpO,KAAM,IAC/F,CAAE7H,KAAM,WAAYD,KAAM,SAAUE,MAAO,WAAY4H,KAAM,GAC7D,CAAE7H,KAAM,SAAUD,KAAM,SAAUE,MAAO,SAAUtC,QAAS,CAAC,MAAO,OAAQ,SAAU,WAAY,KAAM,WAAYkK,KAAM,GAC1H,CAAE9H,KAAM,aAAcC,KAAM,WAAYC,MAAO,WAAYvC,WAAYyK,EAAAA,SAAUjI,WAAY,eAAgBC,WAAY,KAAM0H,KAAM,IACrI,CAAE9H,KAAM,aAAcC,KAAM,WAAYC,MAAO,WAAYvC,WAAYuL,aAAc/I,WAAY,QAASC,WAAY,KAAM0H,KAAM,OAQ9I,MAAM0O,mBAAmBnZ,EAAAA,MACrB,WAAAC,CAAYC,EAAO,IACfC,MAAMD,EAAM,CACRE,SAAU,6BAElB,EAGJ,MAAMgZ,uBAAuB9Y,EAAAA,WACzB,WAAAL,CAAYM,EAAU,IAClBJ,MAAM,CACFK,WAAY2Y,WACZ/Y,SAAU,+BACPG,GAEX,EClGJ,MAAM8Y,iBAAiBC,EAAAA,aACrB,WAAArZ,CAAYM,EAAU,IACpBJ,MAAM,CACJoZ,QAAS,KACTC,UAAW,YACXC,gBAAgB,KACblZ,IAILK,KAAKyC,QAAU9C,EAAQ8C,SAAW,GAClCzC,KAAK8Y,QAAUnZ,EAAQmZ,SAAW,KAClC9Y,KAAK+Y,YAAcpZ,EAAQoZ,aAAe,KAC1C/Y,KAAKgZ,aAAerZ,EAAQqZ,cAAgB,KAC5ChZ,KAAKiZ,UAAYtZ,EAAQsZ,WAAatZ,EAAQuZ,UAAY,KAG1DlZ,KAAKmZ,gCAAmBpF,IAGxB/T,KAAKmD,SAAWnD,KAAKoZ,kBACvB,CAUA,oBAAAC,CAAqBC,GACnB,IAAKA,EAAY,MAAO,GAExB,MAAMC,EAAmB,CAAC,KAAM,KAAM,KAAM,KAAM,OAGlD,GAA0B,iBAAfD,EACT,OAAKC,EAAiB3L,SAAS0L,GAIxB,YAAYA,gBAHjBtU,QAAQgC,KAAK,kCAAkCsS,yBAAkCC,EAAiBC,KAAK,SAChG,IAMX,GAA0B,iBAAfF,EAAyB,CAClC,MAAMG,EAAU,GAGhB,GAAIH,EAAWvQ,KAAM,CACnB,IAAKwQ,EAAiB3L,SAAS0L,EAAWvQ,MAExC,OADA/D,QAAQgC,KAAK,4BAA4BsS,EAAWvQ,4BAA4BwQ,EAAiBC,KAAK,SAC/F,GAETC,EAAQC,KAAK,kBAAkBJ,EAAWvQ,YAC5C,CAGA,GAAIuQ,EAAWK,KAAM,CACnB,IAAKJ,EAAiB3L,SAAS0L,EAAWK,MAExC,OADA3U,QAAQgC,KAAK,4BAA4BsS,EAAWK,4BAA4BJ,EAAiBC,KAAK,SAC/F,GAEJF,EAAWvQ,KAGd0Q,EAAQC,KAAK,KAAKJ,EAAWK,mBAF7BF,EAAQC,KAAK,YAAYJ,EAAWK,kBAIxC,CAEA,OAAOF,EAAQD,KAAK,IACtB,CAEA,MAAO,EACT,CAKA,gBAAAJ,GACE,IAAIjW,EAAW,GA8Cf,OA3CInD,KAAKiZ,WAAajZ,KAAKiZ,UAAUW,iBACnCzW,GAAY,ySAadnD,KAAKyC,QAAQuR,QAAQ6F,IACnB,MAGMC,EAAkB,CAHND,EAAOE,OAASF,EAAOjB,WAAa,GAC5B5Y,KAAKqZ,qBAAqBQ,EAAOP,YACrCO,EAAO9O,SAAW,gBAAkB,IACY6I,OAAOoG,GAAKA,GAAGR,KAAK,KACpFS,EAAcja,KAAKka,kBAAkBL,GAG3C,IAAIM,EAAaN,EAAOlV,QACnBwV,GAAcN,EAAO9O,SACxBoP,EAAa,aACHA,GAAcna,KAAKiZ,UAAUmB,YACvCD,EAAana,KAAKiZ,UAAUmB,WAI5BjX,GADEgX,EACU,cAAcL,mBAAiCK,mBAA4BN,EAAOxB,QAAQ4B,SAE1F,cAAcH,mBAAiCD,EAAOxB,QAAQ4B,WAK1Eja,KAAK8Y,QACP3V,GAAYnD,KAAKqa,uBACRra,KAAK+Y,cACd5V,GAAYnD,KAAKsa,4BAGZnX,CACT,CAQC,iBAAA+W,CAAkBL,GAEd,MAAMU,EAAO,SAASV,EAAOxB,MAEvBmC,EAAYX,EAAOW,WAAaX,EAAOY,OAC7C,GAAID,EAAW,CAEb,GAAyB,iBAAdA,EACT,MAAO,MAAMD,KAAQC,OACvB,GAAgC,mBAAdA,EAChB,MAAO,yBAAyBX,EAAOxB,UAAUkC,YAErD,CAEA,OAAIV,EAAO1W,SACF0W,EAAO1W,SAIZ0W,EAAO9O,SACF,0CAA0C8O,EAAOxB,WAAWkC,cAG9D,MAAMA,MACjB,CAKD,oBAAAF,GACE,OAAKra,KAAK8Y,SAAmC,IAAxB9Y,KAAK8Y,QAAQhG,OAiD3B,2CA/CS9S,KAAK8Y,QAAQV,IAAIzT,IAC/B,GAAsB,iBAAXA,EACT,OAAQA,GACN,IAAK,OACH,MAAO,kOAQT,IAAK,OACH,MAAO,uOAQT,IAAK,SACH,MAAO,uOAQT,QACE,MAAO,QAEb,GAA6B,iBAAXA,EAChB,MAAO,yCACuBA,EAAOoV,OAAS,sDACzB/Z,KAAKmB,MAAMlB,uCACP0E,EAAOA,qCACbA,EAAO1C,OAAS,qBAC7B0C,EAAO+V,KAAO,aAAa/V,EAAO+V,aAAe,mBACjD/V,EAAO1C,QAAU0C,EAAO+V,KAAO/V,EAAO1C,MAAQ,oCAItD,MAAO,KACNuX,KAAK,iBA/C+C,EAkDzD,CAKA,wBAAAc,GACE,OAAKta,KAAK+Y,aAA2C,IAA5B/Y,KAAK+Y,YAAYjG,OAEnC,2cAWG9S,KAAK2a,8EAbgD,EAkBjE,CAKA,qBAAAA,GACE,OAAO3a,KAAK+Y,YAAYX,IAAIwC,IAC1B,GAAIA,EAASC,WAAWD,EAASE,QAC/B,MAAO,yCAGT,IAAIC,EAAY,gBAQhB,OAPwB,WAApBH,EAASjW,QAAuBiW,EAASI,UAC3CD,GAAa,gBAEXH,EAAS9V,WACXiW,GAAa,aAGR,uCAESA,+EAEMH,EAASjW,yBACtBiW,EAAS9V,SAAW,qCAAuC,oBAC5D8V,EAASF,KAAO,aAAaE,EAASF,kBAAoB,mBAC1DE,EAAS3Y,iDAIhBuX,KAAK,GACV,CAKA,mBAAMyB,SACE1b,MAAM0b,gBAGZjb,KAAKyC,QAAQuR,QAAQ6F,IACnB,GAAIA,EAAOW,WAAyC,mBAArBX,EAAOW,UAA0B,CAC9D,MAAMU,EAAOlb,KAAK6E,QAAQsW,cAAc,oBAAoBtB,EAAOxB,SACnE,GAAI6C,EAAM,CACR,MAAMpR,EAAQ9J,KAAKmB,MAAMmJ,IAAMtK,KAAKmB,MAAMmJ,IAAIuP,EAAOxB,KAAOrY,KAAKmB,MAAM0Y,EAAOxB,KACxE+C,EAAU,CACdtR,QACAuR,IAAKrb,KAAKmB,MACVA,MAAOnB,KAAKmB,MACZ0Y,SACAyB,MAAOtb,KAAKiZ,UACZsC,MAAOvb,KAAKub,OAEd,IACEL,EAAKM,UAAY3B,EAAOW,UAAU1Q,EAAOsR,EAC3C,OAAS/a,GACP2E,QAAQ3E,MAAM,oCAAoCwZ,EAAOxB,OAAQhY,EACnE,CACF,CACF,IAaEL,KAAKyb,UACPzb,KAAK6E,QAAQ6W,UAAUzH,IAAI,YAI7B,MAAMhU,EAAKD,KAAKmB,MAAMmJ,IAAMtK,KAAKmB,MAAMmJ,IAAI,MAAQtK,KAAKmB,MAAMlB,GAC1DA,GACFD,KAAK6E,QAAQ8W,aAAa,UAAW1b,EAEzC,CAKA,sBAAM2b,CAAiBhX,EAAOC,GAC5BD,EAAMiX,kBAEN,MAAMC,EAAYjX,EAAQkX,aAAa,eACjClC,EAAS7Z,KAAKyC,QAAQuZ,KAAKC,GAAOA,EAAI5D,MAAQyD,GAE/CjC,GAAWA,EAAO9O,WAGnB/K,KAAKmZ,aAAa+C,IAAIJ,UAEpB9b,KAAKmc,cAAcL,EAAWjC,EAAQhV,GAC9C,CAKA,sBAAMuX,CAAiBxX,EAAOC,GAExBD,EAAMyX,OAAOC,QAAQ,eAAiB1X,EAAMyX,OAAOC,QAAQ,cAAgB1X,EAAMyX,OAAOC,QAAQ,kBAKpGtc,KAAK+E,KAAK,YAAa,CACrBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZ0Y,OAAQhV,EAAQkX,aAAa,eAC7BnX,UAIE5E,KAAKiZ,WACPjZ,KAAKiZ,UAAUlU,KAAK,YAAa,CAC/BsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZ0Y,OAAQhV,EAAQkX,aAAa,eAC7BnX,UAGN,CAKA,kBAAM2X,CAAa3X,EAAOC,GACxBD,EAAMiX,kBAEN7b,KAAK+E,KAAK,WAAY,CACpBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZyD,UAGE5E,KAAKiZ,WACPjZ,KAAKiZ,UAAUlU,KAAK,WAAY,CAC9BsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZyD,SAGN,CAKA,kBAAM4X,CAAa5X,EAAOC,GAgBtB,OAfFD,EAAMiX,kBAEN7b,KAAK+E,KAAK,WAAY,CACpBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZyD,UAGE5E,KAAKiZ,WACPjZ,KAAKiZ,UAAUlU,KAAK,WAAY,CAC9BsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZyD,WAGK,CACX,CAKA,oBAAM6X,CAAe7X,EAAOC,GAC1BD,EAAMiX,kBAEN7b,KAAK+E,KAAK,aAAc,CACtBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZyD,UAGE5E,KAAKiZ,WACPjZ,KAAKiZ,UAAUlU,KAAK,aAAc,CAChCsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZyD,SAGN,CAKA,mBAAMuX,CAAcL,EAAWjC,EAAQ6C,GACrC,MAAMC,EAAcD,EAAYvB,cAAc,iBAC9C,IAAKwB,EAAa,OAElB3c,KAAKmZ,aAAalF,IAAI6H,GACtB,MAAMc,EAAe5c,KAAKmB,MAAMmJ,IAAMtK,KAAKmB,MAAMmJ,IAAIwR,GAAa9b,KAAKmB,MAAM2a,GAGvEe,EAAS7c,KAAK8c,iBAAiBjD,EAAQ+C,GAGvCG,EAAkBJ,EAAYnB,UACpCmB,EAAYK,MAAMC,QAAU,OAE5B,MAAMC,EAAkBC,SAASC,cAAc,OAC/CF,EAAgBtE,UAAY,cAC5BsE,EAAgB1B,UAAYqB,EAC5BH,EAAYW,YAAYH,GAGxB,MAAMI,EAAQJ,EAAgB/B,cAAc,oCACxCmC,IACFA,EAAMC,QACa,SAAfD,EAAMvb,MAAkC,aAAfub,EAAMvb,MACjCub,EAAME,UAKVN,EAAgBO,QAAQV,gBAAkBA,EAC1CG,EAAgBO,QAAQ3B,UAAYA,EAGpC9b,KAAK0d,kBAAkBR,EAAiBpB,EAAWjC,GAEnD7Z,KAAK+E,KAAK,YAAa,CACrBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZ0Y,OAAQiC,EACR6B,cAAef,GAEnB,CAKA,gBAAAE,CAAiBjD,EAAQ+C,GACvB,MAAMjd,EAAUka,EAAO+D,iBAAmB,CAAA,EAE1C,OAAQje,EAAQoC,MACd,IAAK,SACH,OAAO/B,KAAK6d,mBAAmBle,EAASid,GAC1C,IAAK,SACL,IAAK,WACH,OAAO5c,KAAK8d,mBAAmBne,EAASid,GAC1C,IAAK,WACH,OAAO5c,KAAK+d,qBAAqBpe,EAASid,GAC5C,QACE,OAAO5c,KAAKge,iBAAiBre,EAASid,GAE5C,CAKA,gBAAAoB,CAAiBre,EAASid,GACxB,MAAMva,EAAc1C,EAAQ0C,aAAe,GAG3C,MAAO,+EAFW1C,EAAQse,WAAa,kGAMnBje,KAAKke,WAAWtB,GAAgB,qCAC1Bva,mUAS5B,CAKA,oBAAA0b,CAAqBpe,EAASid,GAC5B,MAAMva,EAAc1C,EAAQ0C,aAAe,GAG3C,MAAO,kIAFM1C,EAAQ2W,MAAQ,sCAMAjU,MAAgBrC,KAAKke,WAAWtB,GAAgB,0ZAW/E,CAKA,kBAAAiB,CAAmBle,EAASid,GAC1B,MAAMuB,EAAexe,EAAQA,SAAW,GACxC,IAAIye,EAAc,GAYlB,OAVAD,EAAanK,QAAQqK,IACnB,GAAsB,iBAAXA,EAETD,GAAe,kBAAkBC,MADhBA,IAAWzB,EAAe,WAAa,MACAyB,qBAC7B,iBAAXA,QAAwC,IAAjBA,EAAOvU,MAAqB,CACnE,MAAM2R,EAAW4C,EAAOvU,QAAU8S,EAAe,WAAa,GAC9DwB,GAAe,kBAAkBC,EAAOvU,UAAU2R,KAAY4C,EAAOpc,OAASoc,EAAOvU,gBACvF,IAGK,oIAGCsU,oVAUV,CAKA,kBAAAN,CAAmBne,EAASid,GAC1B,MAAM0B,EAAU1B,EAAe,UAAY,GAG3C,MAAO,yFAF6B,WAAjBjd,EAAQoC,KAAoB,cAAgB,8EAKIuc,kZAYrE,CAKA,iBAAAZ,CAAkBR,EAAiBpB,EAAWjC,GAC5C,MAAMyD,EAAQJ,EAAgB/B,cAAc,eACtCoD,EAAUrB,EAAgB/B,cAAc,cACxCqD,EAAYtB,EAAgB/B,cAAc,iBAG5CmC,GAAyB,SAAfA,EAAMvb,MAAkC,UAAfub,EAAMvb,MAAmC,WAAfub,EAAMvb,MACrEub,EAAMmB,iBAAiB,UAAYC,IACnB,UAAVA,EAAErG,KACJqG,EAAEC,iBACF3e,KAAK4e,aAAa1B,EAAiBpB,EAAWjC,IAC3B,WAAV6E,EAAErG,MACXqG,EAAEC,iBACF3e,KAAK6e,eAAe3B,EAAiBpB,OAMvCwB,GAAyB,aAAfA,EAAMvb,MAAyC,WAAlBub,EAAM3E,UAA6C,IAApBkB,EAAOiF,UAC/ExB,EAAMmB,iBAAiB,SAAU,KAC/Bze,KAAK4e,aAAa1B,EAAiBpB,EAAWjC,KAKlD0E,GAASE,iBAAiB,QAAS,KACjCze,KAAK4e,aAAa1B,EAAiBpB,EAAWjC,KAGhD2E,GAAWC,iBAAiB,QAAS,KACnCze,KAAK6e,eAAe3B,EAAiBpB,IAEzC,CAKA,kBAAM8C,CAAa1B,EAAiBpB,EAAWjC,GAC7C,MAAMyD,EAAQJ,EAAgB/B,cAAc,eAC5C,IAAKmC,EAAO,OAEZ,IAAIyB,EAIFA,EADiB,aAAfzB,EAAMvb,KACGub,EAAMgB,SACRhB,EAAM3E,QACJ2E,EAAMxT,OAKnB,MAAMkV,EAAWhf,KAAKmB,MAAMmJ,IAAMtK,KAAKmB,MAAMmJ,IAAIwR,GAAa9b,KAAKmB,MAAM2a,GAGzE,IACM9b,KAAKmB,MAAM0H,WACP7I,KAAKmB,MAAM0H,KAAK,CAAEiT,CAACA,GAAYiD,IAGrC/e,KAAKmB,MAAM2a,GAAaiD,EAI1B/e,KAAKif,aAAa/B,EAAiBpB,EAAWiD,GAG9C/e,KAAK+E,KAAK,YAAa,CACrBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZ0Y,OAAQiC,EACRkD,WACAD,YAGJ,OAAS1e,GAEP2E,QAAQ3E,MAAM,4BAA6BA,GAC3CL,KAAK+E,KAAK,kBAAmB,CAC3BsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZ0Y,OAAQiC,EACRkD,WACAD,WACA1e,UAIF6c,EAAgBxB,UAAUzH,IAAI,gBAC9BnL,WAAW,IAAMoU,EAAgBxB,UAAUwD,OAAO,gBAAiB,IACrE,CACF,CAKA,cAAAL,CAAe3B,EAAiBpB,GAC9B,MAAMiB,EAAkBG,EAAgBO,QAAQV,gBAChD/c,KAAKif,aAAa/B,EAAiBpB,EAAW,KAAMiB,GAEpD/c,KAAK+E,KAAK,cAAe,CACvBsW,IAAKrb,KACLmB,MAAOnB,KAAKmB,MACZ0Y,OAAQiC,GAEZ,CAKA,YAAAmD,CAAa/B,EAAiBpB,EAAWiD,EAAW,KAAMhC,EAAkB,MAC1E,MACMJ,EADcO,EAAgBZ,QAAQ,MACZnB,cAAc,iBAE9C,GAAIwB,EAAa,CACf,GAAiB,OAAboC,EAAmB,CAErB,MAAMlF,EAAS7Z,KAAKyC,QAAQuZ,KAAKC,GAAOA,EAAI5D,MAAQyD,GACpD,IAAIqD,EAAeJ,EAEflF,GAAUA,EAAOW,WAAyC,iBAArBX,EAAOW,YAC9C2E,EAAe5b,EAAAA,cAAcC,KAAKub,EAAUlF,EAAOW,YAGrDmC,EAAYnB,UAAYxb,KAAKke,WAAWiB,EAC1C,MAAWpC,IAETJ,EAAYnB,UAAYuB,GAG1BJ,EAAYK,MAAMC,QAAU,EAC9B,CAGAC,EAAgBgC,SAChBlf,KAAKmZ,aAAaiG,OAAOtD,EAC3B,CAKA,UAAAoC,CAAWnU,GACT,GAAIA,QAAqC,MAAO,GAChD,MAAMsV,EAAMlC,SAASC,cAAc,OAEnC,OADAiC,EAAIC,YAAcvV,EACXsV,EAAI7D,SACb,CAKA,MAAAgC,GACEje,MAAMie,SACNxd,KAAKuf,SAAS,YAGd,MAAMC,EAAaxf,KAAK6E,SAASsW,cAAc,qBAC3CqE,GACFA,EAAW9D,UAAUzH,IAAI,WAE7B,CAKA,QAAAwL,GACElgB,MAAMkgB,WACNzf,KAAK0f,YAAY,YAGjB,MAAMF,EAAaxf,KAAK6E,SAASsW,cAAc,qBAC3CqE,GACFA,EAAW9D,UAAUwD,OAAO,WAEhC,EC5wBU,MAACS,EAAU,CAErBC,MAAS,CACP3C,QAAS,KACTtX,YAAa,eAEfka,GAAM,CACJ5C,QAAS,KACTtX,YAAa,6CAEfma,IAAO,CACL7C,QAAS,SACTtX,YAAa,kBAEfoa,OAAU,CACR9C,QAAS,SACTtX,YAAa,oCAEfqa,GAAM,CACJ/C,QAAS,IACTtX,YAAa,gBAEfsa,IAAO,CACLhD,QAAS,KACTtX,YAAa,4BAEfua,GAAM,CACJjD,QAAS,IACTtX,YAAa,aAEfwa,IAAO,CACLlD,QAAS,KACTtX,YAAa,yBAIfya,SAAY,CACVnD,QAAS,WACTtX,YAAa,uCAEf0a,UAAa,CACXpD,QAAS,WACTtX,YAAa,yCAEf2a,WAAc,CACZrD,QAAS,cACTtX,YAAa,0CAEf4a,YAAe,CACbtD,QAAS,cACTtX,YAAa,4CAEf6a,SAAY,CACVvD,QAAS,YACTtX,YAAa,wCAEf8a,UAAa,CACXxD,QAAS,YACTtX,YAAa,0CAIf+a,OAAU,CACRzD,QAAU0D,GAAgB,SAARA,IAA0B,IAARA,EAAe,UAAY,cAC/Dhb,YAAa,iCAIfib,MAAS,CACP3D,QAAS,UACTtX,YAAa,yCAeV,SAASkb,EAAeC,GAC7B,IAAKA,GAAgC,iBAAbA,EACtB,MAAO,CAAEC,MAAOD,EAAUhK,OAAQ,MAGpC,MAAMlE,EAAQkO,EAASjO,MAAM,MAG7B,GAAqB,IAAjBD,EAAME,OACR,MAAO,CAAEiO,MAAOD,EAAUhK,OAAQ,MAIpC,MAAMkK,EAAiBpO,EAAMA,EAAME,OAAS,GAC5C,OAAI6M,EAAQqB,GACH,CACLD,MAAOnO,EAAMqO,MAAM,GAAG,GAAIzH,KAAK,MAC/B1C,OAAQkK,GAKL,CAAED,MAAOD,EAAUhK,OAAQ,KACpC,CAoBO,SAASoK,EAAoBJ,EAAUhX,EAAO7H,GACnD,IAAK6e,GAAD,MAAahX,EACf,MAAO,GAGT,MAAMiX,MAAEA,EAAAjK,OAAOA,GAAW+J,EAAeC,GACnCK,EAAYxB,EAAQ7I,GAGpBsK,EAAWlN,MAAMgB,QAAQpL,GAASA,EAAM0P,KAAK,KAAO6H,OAAOvX,GAGjE,IAAKgN,GAAqB,UAAXA,EACb,MAAO,GAAG7U,SAAamf,KAIzB,GAAe,OAAXtK,GAA8B,WAAXA,EAAqB,CAC1C,MAAMwK,EAASF,EAASvO,MAAM,KAAKuF,IAAImJ,GAAKA,EAAEC,QAAQ5N,UAAY2N,GAClE,GAAsB,IAAlBD,EAAOxO,OACT,MAAO,GAAG7Q,KAASkf,EAAUlE,UAE/B,MAAMwE,EAAkBH,EAAOlJ,IAAImJ,GAAK,IAAIA,MAAM/H,KAAK,MACvD,MAAO,GAAGvX,KAASkf,EAAUlE,WAAWwE,GAC1C,CAGA,GAAe,UAAX3K,EAAoB,CACtB,MAAMwK,EAASF,EAASvO,MAAM,KAAKuF,IAAImJ,GAAKA,EAAEC,QAAQ5N,UAAY2N,GAClE,OAAsB,IAAlBD,EAAOxO,OACF,GAAG7Q,cAAkBqf,EAAO,YAAYA,EAAO,MAEjD,GAAGrf,KAASkf,EAAUlE,YAAYmE,IAC3C,CAGA,MAAe,WAAXtK,EAIK,GAAG7U,KAHuC,mBAAtBkf,EAAUlE,QACjCkE,EAAUlE,QAAQmE,GAClBD,EAAUlE,UAKZkE,EACK,GAAGlf,KAASkf,EAAUlE,YAAYmE,KAIpC,GAAGnf,SAAamf,IACzB,CA4DA,MAAAM,EAAe,CACb/B,UACAkB,iBACAK,sBACAS,qBApDK,SAA8B7K,GACnC,MAAMqK,EAAYxB,EAAQ7I,GAC1B,OAAOqK,EAAYA,EAAUxb,YAAc,aAC7C,EAkDEic,cAtCK,SAAuB9K,GAC5B,OAAOA,GAAU6I,EAAQkC,eAAe/K,EAC1C,EAqCEgL,oBA3BK,WACL,OAAO5J,OAAO6J,KAAKpC,EACrB,EA0BEqC,eAbK,SAAwBjB,EAAOjK,EAAS,MAC7C,OAAKiK,EACAjK,EACE,GAAGiK,MAAUjK,IADAiK,EADD,EAGrB,GC/NA,MAAMkB,kBAAkBC,EAAAA,SACtB,WAAA7iB,CAAYM,EAAU,IAWpBJ,MATqB,CACnBqZ,UAAW,uBACXmC,UAAWpb,EAAQob,WAAatC,SAChC0J,cAAexiB,EAAQyiB,WAAa,WAAa,OACjDC,aAAc1iB,EAAQ0iB,cAAgB,oBACtCC,cAAe3iB,EAAQ2iB,eAAiB,uBACrC3iB,IAMLK,KAAKuiB,cAAe,EAGpBviB,KAAKyC,QAAU9C,EAAQ8C,SAAW,GAClCzC,KAAK8Y,QAAUnZ,EAAQmZ,SAAW,KAClC9Y,KAAK+Y,YAAcpZ,EAAQoZ,aAAe,KAC1C/Y,KAAKgZ,aAAerZ,EAAQqZ,cAAgB,KAC5ChZ,KAAKwiB,YAAoC,IAAvB7iB,EAAQ6iB,WAC1BxiB,KAAKyiB,UAAgC,IAArB9iB,EAAQ8iB,SACxBziB,KAAK0iB,YAAoC,IAAvB/iB,EAAQ+iB,WAC1B1iB,KAAK2iB,WAAkC,IAAtBhjB,EAAQgjB,UACzB3iB,KAAK4iB,YAAcjjB,EAAQijB,aAAe,OAG1C5iB,KAAK6iB,SAAWljB,EAAQkjB,SACxB7iB,KAAK8iB,QAAUnjB,EAAQmjB,QACvB9iB,KAAK+iB,SAAWpjB,EAAQojB,SACxB/iB,KAAKgjB,eAAiBrjB,EAAQqjB,eAC9BhjB,KAAKijB,iBAAmBtjB,EAAQsjB,kBAAoB,CAAA,EACpDjjB,KAAKkjB,kBAAoBvjB,EAAQujB,mBAAqB,CAAA,EAGtDljB,KAAKmjB,cAAgBxjB,EAAQwjB,eAAiB,KAC1CnjB,KAAKL,QAAQyjB,aAAepjB,KAAKmjB,gBACnCnjB,KAAKmjB,cAAgB,CACnB,CAAE1I,OAAQ,MAAOxY,MAAO,gBAAiByY,KAAM,kCAC/C,CAAED,OAAQ,OAAQxY,MAAO,iBAAkByY,KAAM,6BAGrD1a,KAAKqjB,aAAe1jB,EAAQ0jB,cAAgB,SAG5CrjB,KAAKsjB,QAAU,CAAA,EACftjB,KAAKujB,kBAAoB5jB,EAAQ2jB,SAAW,GAC5CtjB,KAAKwjB,gBAAkB7jB,EAAQ6jB,kBAAmB,EAClDxjB,KAAKyjB,oBAAsB9jB,EAAQ8jB,qBAAuB,GAC1DzjB,KAAKoa,UAAYza,EAAQya,WAAa,YACtCpa,KAAK0jB,iBAAmB/jB,EAAQ+jB,kBAAoB,SAEpD1jB,KAAKL,QAAQgkB,eAAiBhkB,EAAQgkB,gBAAkB,MAGxD3jB,KAAK4jB,eAAiBjkB,EAAQikB,gBAAkB,GAGhD5jB,KAAK6jB,aAAe,CAClBC,SAAS,EACTC,UAAU,EACVC,OAAO,EACPC,YAAY,EACZpkB,KAAM,QACHF,EAAQkkB,cAIb7jB,KAAKkkB,gBAAkBvkB,EAAQukB,iBAAmB,UAClDlkB,KAAKmkB,kBAAoBxkB,EAAQwkB,mBAAqB,YAGtDnkB,KAAKokB,oBAGLpkB,KAAKqkB,uBAGLrkB,KAAKskB,mBAAqBtkB,KAAKyC,QAAQmR,OAAOqI,IAA4B,IAArBA,EAAIsI,cACzDvkB,KAAKwkB,gBAAkBxkB,KAAKskB,mBAAmBxR,OAAS,EAGxD9S,KAAKmD,SAAWnD,KAAKykB,qBAGrBzkB,KAAK0kB,0BACP,CAKA,wBAAAA,GACM1kB,KAAKwkB,iBAAmBxkB,KAAK2kB,YAE/B3kB,KAAK2kB,WAAWC,GAAG,0BAA2B,KAC5C5kB,KAAK6kB,sBAGX,CAKA,iBAAAT,GACEpkB,KAAKyC,QAAQuR,QAAQ6F,KAEdA,EAAOxB,KAAOwB,EAAO7X,OACxB6X,EAAOxB,IAAMwB,EAAO7X,MAIjB6X,EAAO5X,OAAU4X,EAAOhY,QAC3BgY,EAAO5X,MAAQ4X,EAAOxB,IAAIyM,OAAO,GAAG/jB,cAAgB8Y,EAAOxB,IAAI4I,MAAM,KAG3E,CAUA,oBAAA5H,CAAqBC,GACnB,IAAKA,EAAY,MAAO,GAExB,MAAMC,EAAmB,CAAC,KAAM,KAAM,KAAM,KAAM,OAGlD,GAA0B,iBAAfD,EACT,OAAKC,EAAiB3L,SAAS0L,GAIxB,YAAYA,gBAHjBtU,QAAQgC,KAAK,kCAAkCsS,yBAAkCC,EAAiBC,KAAK,SAChG,IAMX,GAA0B,iBAAfF,EAAyB,CAClC,MAAMG,EAAU,GAGhB,GAAIH,EAAWvQ,KAAM,CACnB,IAAKwQ,EAAiB3L,SAAS0L,EAAWvQ,MAExC,OADA/D,QAAQgC,KAAK,4BAA4BsS,EAAWvQ,4BAA4BwQ,EAAiBC,KAAK,SAC/F,GAETC,EAAQC,KAAK,kBAAkBJ,EAAWvQ,YAC5C,CAGA,GAAIuQ,EAAWK,KAAM,CACnB,IAAKJ,EAAiB3L,SAAS0L,EAAWK,MAExC,OADA3U,QAAQgC,KAAK,4BAA4BsS,EAAWK,4BAA4BJ,EAAiBC,KAAK,SAC/F,GAEJF,EAAWvQ,KAGd0Q,EAAQC,KAAK,KAAKJ,EAAWK,mBAF7BF,EAAQC,KAAK,YAAYJ,EAAWK,kBAIxC,CAEA,OAAOF,EAAQD,KAAK,IACtB,CAEA,MAAO,EACT,CAKA,cAAAuL,CAAe1M,GACb,MAAMzF,EAAQyF,EAAIxF,MAAM,KACxB,MAAO,CACLmS,SAAUpS,EAAM,GAChB4H,UAAW5H,EAAM,IAAM,KAE3B,CAKA,kBAAAiS,GACE,IAAK7kB,KAAKwkB,kBAAoBxkB,KAAK6E,QAAS,OAE5C,MAAMogB,EAASjlB,KAAKklB,wBAIpB,IAAIC,EAAmB,EACvBnlB,KAAKyC,QAAQuR,QAAS6F,IACpB,GAAIA,EAAO0K,aAAc,CACvB,MAAMa,EAAU,OAAOD,IACjBjK,EAAOlb,KAAK6E,QAAQsW,cAAc,uBAAuBiK,OAE/D,GAAIlK,GAAQ+J,EAAOG,GAAU,CAC3B,MAAM5K,EAAYxa,KAAK+kB,eAAelL,EAAOxB,KAAKmC,WAAaX,EAAOW,UACtE,IAAI2E,EAIFA,EAFE3E,GAAkC,iBAAdA,EAEPxa,KAAKqlB,YAAYJ,EAAOG,GAAStb,MAAO0Q,GAExCyK,EAAOG,GAAStb,MAGjCoR,EAAKoE,YAAcH,CACrB,CACAgG,GACF,GAEJ,CAKA,WAAAE,CAAYvb,EAAO0Q,GACjB,IACE,OAAOjX,gBAAcC,KAAKsG,EAAO0Q,EACnC,OAASkE,GAEP,OADA1Z,QAAQgC,KAAK,0BAA2B0X,GACjC5U,CACT,CACF,CAKA,qBAAAob,GACE,IAAKllB,KAAKwkB,kBAAoBxkB,KAAK2kB,YAAyC,IAA3B3kB,KAAK2kB,WAAW7R,OAC/D,MAAO,CAAA,EAGT,MAAMmS,EAAS,CAAA,EA4Bf,OA1BAjlB,KAAKskB,mBAAmBtQ,QAAQ,CAAC6F,EAAQsL,KACvC,MAAMH,SAAEA,EAAAxK,UAAUA,GAAcxa,KAAK+kB,eAAelL,EAAOxB,KAC3D,IAAIiN,EAAM,EAGVtlB,KAAK2kB,WAAW3Q,QAAQ7S,IACtB,MAAM2I,EAAQ3I,EAAMmJ,IAAMnJ,EAAMmJ,IAAI0a,GAAY7jB,EAAM6jB,GAChDO,EAAWC,WAAW1b,IAAU,EACtCwb,GAAOC,IAIuB1L,EAAOxB,IAAqBrY,KAAK2kB,WAAW7R,OAM5EmS,EAHgB,OAAOE,KAGL,CAChBrb,MAAOwb,EACP9K,UAAWA,GAAaX,EAAOW,UAC/BwK,WACAS,YAAa5L,EAAOxB,OAIjB4M,CACT,CAKA,oBAAAZ,GACErkB,KAAKsjB,QAAU,CAAA,EACftjB,KAAKyC,QAAQuR,QAAQ6F,IACnB,GAAIA,EAAOjG,OAAQ,CACjB,MAAMoR,SAAEA,GAAahlB,KAAK+kB,eAAelL,EAAOxB,KAChDrY,KAAKsjB,QAAQ0B,GAAYnL,EAAOjG,MAClC,GAEJ,CAEA,YAAAgG,GACI,OAAO5Z,KAAKgZ,cAAgBhZ,KAAKgZ,aAAalG,OAAS,GAA2B,YAAtB9S,KAAKmiB,aACrE,CAKA,kBAAAsC,GACE,MAAMiB,EAA0C,QAA1B1lB,KAAK0jB,iBAA6B1jB,KAAK2lB,yBAA2B,GAClFC,EAA6C,WAA1B5lB,KAAK0jB,iBAAgC1jB,KAAK2lB,yBAA2B,GAE9F,MAAO,qDAED3lB,KAAK6lB,mCACLH,0CAAa,MACwB,MAAMI,EAAQ9lB,KAAK6jB,cAA8C,MAA9B7jB,KAAK6jB,aAAakC,SAAoB/lB,KAAK6jB,aAAakC,SAAY/lB,KAAKL,SAAWK,KAAKL,QAAQomB,SAAiBC,EAAiB,OAATF,EAAgB,SAAqB,OAATA,EAAgB,SAAYA,EAAOzE,OAAOyE,GAAQ,KAAQ,OAAOE,EAAQ,sBAAsBA,MAAY,IAD5T,kpBAiBOhmB,KAAKimB,0CACjBjmB,KAAKkmB,uGAELlmB,KAAKwkB,gBAAkBxkB,KAAKmmB,2BAA6B,yGAKjEP,cACA5lB,KAAKomB,+CAGb,CAKA,iBAAAH,GACE,IAAIxM,EAAU,CAAC,SAUf,OARIzZ,KAAK6jB,aAAaC,SAASrK,EAAQC,KAAK,iBACxC1Z,KAAK6jB,aAAaE,UAAUtK,EAAQC,KAAK,kBACzC1Z,KAAK6jB,aAAaG,OAAOvK,EAAQC,KAAK,eACtC1Z,KAAK6jB,aAAaI,YAAYxK,EAAQC,KAAK,oBAC3C1Z,KAAK6jB,aAAawC,YAAY5M,EAAQC,KAAK,SAAS1Z,KAAK6jB,aAAawC,cAC3C,OAA3BrmB,KAAK6jB,aAAahkB,MAAe4Z,EAAQC,KAAK,YACnB,OAA3B1Z,KAAK6jB,aAAahkB,MAAe4Z,EAAQC,KAAK,YAE3CD,EAAQD,KAAK,IACtB,CAKA,oBAAAqM,GACE,OAAK7lB,KAAKwiB,YAAexiB,KAAK0iB,WAIvB,qHAGC1iB,KAAKsmB,2CACLtmB,KAAK0iB,WAAa1iB,KAAKumB,8BAAgC,iBACvDvmB,KAAKwiB,YAAuC,YAAzBxiB,KAAKkkB,gBAAgClkB,KAAKwmB,sBAAwB,8FARpF,EAcX,CAKA,0BAAAF,GACE,IAAIG,EAAU,GAkCd,GA/BAA,EAAQ/M,KAAK,mNAST1Z,KAAK0mB,yBACPD,EAAQ/M,KAAK,iPAUX1Z,KAAKL,QAAQgnB,SACfF,EAAQ/M,KAAK,uHAGM1Z,KAAKL,QAAQgkB,yCAChB3jB,KAAKL,QAAQ2iB,wEACUtiB,KAAKL,QAAQgkB,oDAKlD3jB,KAAKL,QAAQyjB,WACf,GAAIpjB,KAAKmjB,eAAiBnjB,KAAKmjB,cAAcrQ,OAAS,EAAG,CAEvD,MAAM8T,EAAgB5mB,KAAKmjB,cAAc/K,IAAIyO,GAAO,qGAEsBA,EAAIpM,qCAC5DoM,EAAInM,MAAQ,6CAA6CmM,EAAI5kB,sDAG5EuX,KAAK,IAERiN,EAAQ/M,KAAK,sZAQLkN,mDAIV,KAAO,CAEL,MAAMnM,EAASza,KAAKmjB,eAA+C,IAA9BnjB,KAAKmjB,cAAcrQ,OAAe9S,KAAKmjB,cAAc,GAAG1I,OAAS,OACtGgM,EAAQ/M,KAAK,mJAGYe,oLAM3B,CAiDF,OAzCIza,KAAK4jB,gBAAkB5jB,KAAK4jB,eAAe9Q,OAAS,GACtD9S,KAAK4jB,eAAe5P,QAAQ,CAAC8S,EAAQvL,KACnC,MAAMtZ,MACJA,EAAQ,SAAAyY,KACRA,EAAO,GAAA/V,OACPA,EAAS,GAAAoiB,QACTA,EAAU,KAAAC,QACVA,EAAU,oBAAAnlB,MACVA,EAAQI,EAAA2W,UACRA,EAAY,GAAAvD,YACZA,EAAc,MACZyR,EAGJ,GAAIzR,IAAgBrV,KAAKinB,iBAAiB5R,GACxC,OAGF,MAAM6R,EAAWxM,EAAO,aAAaA,eAAoB,GACnDyM,EAAY,oCAAoCllB,WAGtD,IAAImlB,EAAY,GACZL,EACFK,EAAY,0DAA0D7L,KAC7D5W,IACTyiB,EAAY,gBAAgBziB,MAG9B,MAAM0iB,EAAW,kBAAkBL,KAAWpO,IAAY4I,OAE1DiF,EAAQ/M,KAAK,8BACM2N,yBACPD,+BACOvlB,oBACbqlB,IAAWC,sCAMdV,EAAQjN,KAAK,GACtB,CAKA,mBAAAgN,GACE,MAAO,u0BAuBT,CAKA,2BAAAD,GAIE,OAHoBvmB,KAAKsjB,SAAWpL,OAAO6J,KAAK/hB,KAAKsjB,SAASxQ,OAAS,GACpD9S,KAAKujB,mBAAqBvjB,KAAKujB,kBAAkBzQ,OAAS,EAMtE,oYAQC9S,KAAKsnB,wDAXJ,EAeX,CAKA,eAAAA,GACE,MAAMC,EAAavnB,KAAKwnB,yBAClBC,EAAgBznB,KAAK0nB,mBAE3B,OAA0B,IAAtBH,EAAWzU,OACN,wEAmBF,WAhBayU,EAAWnP,IAAIxE,IACjC,MAAMjG,EAAW8Z,EAAc5F,eAAejO,EAAOyE,KAC/CsP,EAAcha,EAAW,SAAW,GACpC+M,EAAO1a,KAAK4nB,cAAchU,EAAO7R,MAAQ6R,EAAOiU,QAAQ9lB,MAE9D,MAAO,0CAC0B4lB,kFAEJ/T,EAAOyE,oCACdqC,2BAChB9G,EAAO3R,oBACP0L,EAAW,6CAA+C,kCAG/D6L,KAAK,cAIJtB,OAAO6J,KAAK0F,GAAe3U,OAAS,EAAI,gOAKtC,UAER,CAKA,iBAAAgV,GACE,MAAMC,EAAY/nB,KAAK6E,SAASsW,cAAc,mCAE9C,IAAK4M,EACH,OAGoB/nB,KAAK0nB,mBAE3B,MAAMM,EAAYhoB,KAAKioB,mBACvBF,EAAUvM,UAAYwM,CACxB,CAKA,kBAAAE,CAAmBpe,GACjB,MAAMqe,EAAenoB,KAAK6E,SAASujB,iBAAiB,0BAChDD,GACFA,EAAanU,QAAQsJ,IACnBA,EAAMxT,MAAQA,GAAS,IAG7B,CAKA,gBAAAme,GACE,GAAIjoB,KAAKwjB,gBACP,MAAO,GAGT,MAAMiE,EAAgBznB,KAAK0nB,mBACrBW,EAAYZ,EAAca,QAAqD,KAA3Cb,EAAca,OAAOC,WAAW/G,OAC1E,IAAIgH,EAAgBtQ,OAAOC,QAAQsP,GAAe7T,OAAO,EAAEyE,EAAKvO,KAC9DA,GAAqC,KAA5BA,EAAMye,WAAW/G,QAAyB,WAARnJ,GAU7C,OANIrY,KAAKyjB,qBAAuBzjB,KAAKyjB,oBAAoB3Q,OAAS,IAChE0V,EAAgBA,EAAc5U,OAAO,EAAEyE,MACpCrY,KAAKyjB,oBAAoB7V,SAASyK,KAIV,IAAzBmQ,EAAc1V,QAAiBuV,EAyC5B,0IArCOG,EAAcpQ,IAAI,EAAE0I,EAAUhX,MAC1C,MAAMiX,MAAEA,GAAUF,EAAeC,GAKjC,MAAO,gZAOoBA,2DAVPI,EAAoBJ,EAAUhX,EADpC9J,KAAKyoB,eAAe1H,kQAmBPD,+FAK1BtH,KAAK,oBAGagP,EAAc1V,OAAS,GAAM0V,EAAc1V,OAAS,GAAKuV,GAAwC,IAAzBG,EAAc1V,QAAgBuV,EACrF,wQAKlC,2DAtCK,EAkDX,CAKA,wBAAAnC,GACE,IAAIwC,EAAc,GAmElB,OAhEI1oB,KAAK4Z,iBACP8O,GAAe,2QAYjB1oB,KAAKyC,QAAQuR,QAAQ6F,IAEnB,MAAMmL,SAAEA,GAAahlB,KAAK+kB,eAAelL,EAAOxB,KAE1CoK,EAAWziB,KAAKyiB,WAAgC,IAApB5I,EAAO4I,SACnCkG,EAAc3oB,KAAK4oB,cAAgB5D,EAAWhlB,KAAK6oB,mBAAqB,KACxEC,EAAW9oB,KAAK+oB,YAAYJ,GAC5B1mB,EAAQ4X,EAAO5X,OAAS4X,EAAOhY,OAASmjB,EACxCgE,EAAoBhpB,KAAKqZ,qBAAqBQ,EAAOP,YA0B3DoP,GAAe,wBACAjG,EAAW,WAAa,MAAMuG,6EAE/B/mB,yBA3BOwgB,EAAW,iPAILuC,oBACnB8D,2HAG4C,QAAhBH,EAAwB,SAAW,0DACzB3D,oKAGM,SAAhB2D,EAAyB,SAAW,0DAC1B3D,yKAGM,OAAhB2D,EAAuB,SAAW,0DACxB3D,4JAK1C,gDAaFhlB,KAAK8Y,QACP4P,GAAe,mBACN1oB,KAAK+Y,cACd2P,GAAe,iCAGV,4CAGCA,wCAIV,CAKA,wBAAAvC,GACE,IAAI8C,EAAc,GAGdjpB,KAAK4Z,iBACPqP,GAAe,aAIjB,IAAI9D,EAAmB,EAkCvB,OAjCAnlB,KAAKyC,QAAQuR,QAAQ,CAAC6F,EAAQ0B,KAC5B,MAAMyN,EAAoBhpB,KAAKqZ,qBAAqBQ,EAAOP,YAE3D,GAAIO,EAAO0K,aAAc,CAEvB,MAAMa,EAAU,OAAOD,IACjB3K,EAAYxa,KAAK+kB,eAAelL,EAAOxB,KAAKmC,WAAaX,EAAOW,UAEtE,IAAIP,EAEFA,EADEO,GAAkC,iBAAdA,EACR,mBAAmB4K,WAAiB5K,OAEpC,kBAAkB4K,YAGlC6D,GAAe,iCAAiCD,yBAAyC5D,MAAYnL,SACrGkL,GACF,MAEE8D,GAFmB,IAAV1N,EAEM,iCAAiCyN,kCAGjC,cAAcA,cAK7BhpB,KAAK8Y,SAEE9Y,KAAK+Y,eADdkQ,GAAe,aAKV,qEAGCA,wCAIV,CAKA,sBAAAtD,GACE,IAAK3lB,KAAKgZ,cAA6C,IAA7BhZ,KAAKgZ,aAAalG,OAC1C,MAAO,GAGT,GAA8B,QAA1B9S,KAAK0jB,iBAA4B,CAEnC,IAAIwF,EAAc,GAUlB,OATAlpB,KAAKgZ,aAAahF,QAAQrP,IACxBukB,GAAe,mFACyDvkB,EAAOA,kBAAkBA,EAAO1C,kCACxF0C,EAAO+V,iEACgB/V,EAAO1C,gDAKzC,6TAK+CjC,KAAKL,QAAQwpB,iBAAmB,2IAI5ED,qUASZ,CAAO,CAEL,IAAIA,EAAc,GAYlB,OAXAlpB,KAAKgZ,aAAahF,QAAQrP,IACxBukB,GAAe,oFAC0DvkB,EAAOA,uFAE9DA,EAAO+V,qFAEmB/V,EAAO1C,4CAK9C,maAQ0CjC,KAAKL,QAAQwpB,iBAAmB,yKAInED,6OAUhB,CACF,CAKA,uBAAA9C,GACE,OAAKpmB,KAAK2iB,UAIH,6yCAHE,EA8BX,CAKA,eAAAyG,CAAgBjoB,EAAOoa,GACrB,MAAMsH,EAAW,IAAI7iB,KAAK+a,UAAU,CAClC5Z,QACAoa,QACArC,SAAUlZ,KACViZ,UAAWjZ,KACXmD,SAAUnD,KAAKqpB,aACf5mB,QAASzC,KAAKyC,QACdqW,QAAS9Y,KAAK8Y,QACdC,YAAa/Y,KAAK+Y,YAClBC,aAAchZ,KAAKgZ,aACnBsQ,YAAa,UAyBf,OArBAtpB,KAAKupB,UAAU/hB,IAAIrG,EAAMlB,GAAI4iB,GAG7BA,EAAS+B,GAAG,cAAgBhgB,IAC1B5E,KAAKwpB,cAAc5kB,GACnB5E,KAAKypB,4BAEP5G,EAAS+B,GAAG,gBAAkBhgB,IAC5B5E,KAAK0pB,gBAAgB9kB,GACrB5E,KAAKypB,4BAIP5G,EAAS+B,GAAG,YAAa5kB,KAAK2pB,YAAYC,KAAK5pB,OAC/C6iB,EAAS+B,GAAG,WAAY5kB,KAAK6pB,WAAWD,KAAK5pB,OAC7C6iB,EAAS+B,GAAG,WAAY5kB,KAAK8pB,WAAWF,KAAK5pB,OAC7C6iB,EAAS+B,GAAG,aAAc5kB,KAAK+pB,aAAaH,KAAK5pB,OACjD6iB,EAAS+B,GAAG,YAAa5kB,KAAKgqB,YAAYJ,KAAK5pB,OAC/C6iB,EAAS+B,GAAG,YAAa5kB,KAAKiqB,YAAYL,KAAK5pB,OAC/C6iB,EAAS+B,GAAG,cAAe5kB,KAAKkqB,cAAcN,KAAK5pB,OAE5C6iB,CACT,CAKA,eAAMsH,SACE5qB,MAAM4qB,YACZ,MAAM1C,EAAgBznB,KAAK0nB,mBAGvB1nB,KAAK2kB,YAAczM,OAAO6J,KAAK0F,GAAe3U,OAAS,GACzD9S,KAAK8nB,oBAIP9nB,KAAKoqB,0BACP,CAKA,wBAAAA,GACOpqB,KAAK6E,SAEW7E,KAAK6E,QAAQujB,iBAAiB,8CACtCpU,QAAQsJ,IAEnBA,EAAMmB,iBAAiB,QAAU7Z,IAEJ,KAAvBA,EAAMyX,OAAOvS,OAAgB9J,KAAK0nB,mBAAmBY,QACvDtoB,KAAKqqB,oBAAoBzlB,EAAOA,EAAMyX,WAI9C,CAKA,WAAAsN,CAAY/kB,GAIV,GAHA5E,KAAK+E,KAAK,YAAaH,GAGnB5E,KAAKL,QAAQ2qB,WACf,OAAOtqB,KAAKL,QAAQ2qB,WAAW1lB,EAAMzD,MAAOyD,EAAMA,OAG3B,SAArB5E,KAAK4iB,YACP5iB,KAAK6pB,WAAWjlB,GACc,SAArB5E,KAAK4iB,aACd5iB,KAAK8pB,WAAWllB,EAEpB,CAKA,aAAA2lB,CAAcppB,GAEZ,OAAInB,KAAK2kB,YAAY/kB,WAAmBI,KAAK2kB,WAAW/kB,WACpDI,KAAK2kB,YAAYxjB,MAAcnB,KAAK2kB,WAAWxjB,MAG/CA,GAAO9B,YAAoB8B,EAAM9B,YAG9B,IACT,CAKA,YAAAmrB,CAAarpB,GACX,MAAMvB,EAAaI,KAAKuqB,cAAcppB,GACtC,OAAKvB,IAEEA,EAAW6qB,YACX7qB,EAAWoC,KAAK0oB,QAAQ,SAAU,MAHjB,MAK1B,CAKA,gBAAAC,CAAiBxpB,GAEf,GAAInB,KAAK6iB,SAAU,OAAO7iB,KAAK6iB,SAG/B,MAAMjjB,EAAaI,KAAKuqB,cAAcppB,GACtC,OAAIvB,GAAYgrB,WAAmBhrB,EAAWgrB,WAEvC,IACT,CAKA,gBAAAC,CAAiBjrB,GACf,OAAOI,KAAK8iB,SACLljB,GAAY2M,UACZvM,KAAK+iB,UACLnjB,GAAY0M,SACrB,CAKA,iBAAAwe,CAAkBlrB,GAChB,OAAOI,KAAK+iB,UACLnjB,GAAY0M,WACZtM,KAAK8iB,SACLljB,GAAY2M,QACrB,CAKA,mBAAAwe,CAAoBnrB,GAClB,MAAO,IACFA,GAAYorB,sBACZhrB,KAAKijB,iBAEZ,CAKA,oBAAAgI,CAAqB9nB,EAAUhC,GAC7B,OAAKgC,EAGE+nB,WAAS5mB,OAAOnB,EAAUhC,GAHX,EAIxB,CAKA,gBAAM0oB,CAAWjlB,GAIf,GAHA5E,KAAK+E,KAAK,WAAYH,GAGlB5E,KAAKL,QAAQwrB,WAEf,kBADMnrB,KAAKL,QAAQwrB,WAAWvmB,EAAMzD,MAAOyD,EAAMA,QAInD,MAAMwmB,EAAYprB,KAAK2qB,iBAAiB/lB,EAAMzD,OAE9C,GAAIiqB,EAAW,CAEb,MAAMC,EAAe,IAAID,EAAU,CAAEjqB,MAAOyD,EAAMzD,cAC5CmqB,EAAAA,OAAOC,WAAW,CACtBC,QAAQ,EACRC,KAAMJ,EACNxrB,KAAM,KACN6rB,UAAU,KACP1rB,KAAK+qB,oBAAoB/qB,KAAKuqB,cAAc3lB,EAAMzD,WAClDnB,KAAKkjB,mBAEZ,YAEQoI,EAAAA,OAAOK,SAAS,CACpB9pB,MAAO,QAAQ7B,KAAKwqB,aAAa5lB,EAAMzD,WAAWyD,EAAMzD,MAAMlB,KAC9DkB,MAAOyD,EAAMzD,OAGnB,CAKA,gBAAM2oB,CAAWllB,GAIf,GAHA5E,KAAK+E,KAAK,WAAYH,GAGlB5E,KAAKL,QAAQisB,WAEf,kBADM5rB,KAAKL,QAAQisB,WAAWhnB,EAAMzD,MAAOyD,EAAMA,QAInD,MAAMhF,EAAaI,KAAKuqB,cAAc3lB,EAAMzD,OAC5C,IAAI0qB,EAAa7rB,KAAK8qB,kBAAkBlrB,GAExC,GAAIisB,EAAY,CACPA,EAAW/pB,SACZ+pB,EAAa,CAAEhqB,MAAO,QAAQ7B,KAAKwqB,aAAa5lB,EAAMzD,SAAUW,OAAQ+pB,IAG9E,MAAMnlB,QAAe4kB,EAAAA,OAAOQ,cAAc,CACxC3qB,MAAOyD,EAAMzD,SACV0qB,KACA7rB,KAAK+qB,oBAAoBnrB,KAG9B,IAAK8G,EAAQ,OAEb,IAAKA,EAAOvG,UAAYuG,GAAQA,QAAQpH,KAAKc,OAE3C,YADAkrB,SAAOprB,UAAUwG,GAAQA,QAAQpH,MAAMe,OAASqG,GAAQA,QAAQ9F,SAAW,oBAI/E,KAAO,CAGL,MAAM8F,QAAe4kB,EAAAA,OAAOC,WAAW,CACrC1pB,MAAO,QAAQ7B,KAAKwqB,aAAa5lB,EAAMzD,WAAWyD,EAAMzD,MAAMlB,KAC9DwrB,KAAM,IAAIM,EAAAA,SAAS,CACjB5qB,MAAOyD,EAAMzD,MACbW,OAAQ9B,KAAKL,QAAQqsB,YAAc,OAIvC,GAAItlB,EAAQ,CACV,MAAMsQ,QAAapS,EAAMzD,MAAM0H,KAAKnC,GACpC,IAAKsQ,EAAK1X,MAAMc,OAEZ,YADAkrB,EAAAA,OAAOprB,UAAU8W,EAAK1X,KAAKe,OAAS,2BAGlCL,KAAKisB,SACb,CACF,CACF,CAKA,kBAAMlC,CAAanlB,GAIjB,GAHA5E,KAAK+E,KAAK,aAAcH,GAGpB5E,KAAKL,QAAQusB,aAEf,kBADMlsB,KAAKL,QAAQusB,aAAatnB,EAAMzD,MAAOyD,EAAMA,QAIrD,MAAMhF,EAAaI,KAAKuqB,cAAc3lB,EAAMzD,OAGtCgC,EAAWnD,KAAKgjB,gBACNpjB,GAAYusB,iBACZ,yDAGVvrB,EAAUZ,KAAKirB,qBAAqB9nB,EAAUyB,EAAMzD,aAElCmqB,EAAAA,OAAOlb,QAAQ,CACrCxP,QAASA,GAAW,6CACpBiB,MAAO,iBACPuqB,YAAa,SACbC,aAAc,uBAIRznB,EAAMzD,MAAMmrB,UAClBtsB,KAAK2kB,WAAWvV,QAEpB,CAKA,WAAA4a,CAAYplB,GACV5E,KAAK+E,KAAK,YAAaH,EACzB,CAKA,iBAAMqlB,CAAYrlB,GAChB5E,KAAK+E,KAAK,YAAaH,EAEzB,CAKA,aAAAslB,CAActlB,GACZ5E,KAAK+E,KAAK,cAAeH,EAC3B,CAKA,qBAAA8hB,GACE,SACEvJ,SAASoP,mBACTpP,SAASqP,sBACTrP,SAASsP,yBACTtP,SAASuP,oBAEb,CAKA,8BAAMC,CAAyB/nB,EAAOC,GAChC7E,KAAKuiB,mBACDviB,KAAK4sB,uBAEL5sB,KAAK6sB,iBAEf,CAKA,qBAAMA,GACJ,IAEM7sB,KAAK6E,QAAQioB,wBACT9sB,KAAK6E,QAAQioB,oBACV9sB,KAAK6E,QAAQkoB,2BAChB/sB,KAAK6E,QAAQkoB,uBACV/sB,KAAK6E,QAAQmoB,8BAChBhtB,KAAK6E,QAAQmoB,0BACVhtB,KAAK6E,QAAQooB,2BAChBjtB,KAAK6E,QAAQooB,sBAGrBjtB,KAAKuiB,cAAe,EACpBviB,KAAK6E,QAAQ6W,UAAUzH,IAAI,oBAC3BjU,KAAKktB,yBAGLltB,KAAKmtB,2BAELntB,KAAK+E,KAAK,yBAEZ,OAAS1E,GACP2E,QAAQgC,KAAK,8BAA+B3G,EAC9C,CACF,CAKA,oBAAMusB,GACJ,IACMzP,SAASyP,qBACLzP,SAASyP,iBACNzP,SAASiQ,0BACZjQ,SAASiQ,sBACNjQ,SAASkQ,2BACZlQ,SAASkQ,uBACNlQ,SAASmQ,wBACZnQ,SAASmQ,mBAGjBttB,KAAKuiB,cAAe,EACpBviB,KAAK6E,QAAQ6W,UAAUwD,OAAO,oBAC9Blf,KAAKktB,yBAELltB,KAAK+E,KAAK,wBAEZ,OAAS1E,GACP2E,QAAQgC,KAAK,6BAA8B3G,EAC7C,CACF,CAKA,sBAAA6sB,GACE,MAAMpG,EAAS9mB,KAAK6E,SAASsW,cAAc,mBACrCT,EAAOoM,GAAQ3L,cAAc,KAE/B2L,GAAUpM,IACR1a,KAAKuiB,cACP7H,EAAK9B,UAAY,wBACjBkO,EAAOjlB,MAAQ,oBAEf6Y,EAAK9B,UAAY,mBACjBkO,EAAOjlB,MAAQ,oBAGrB,CAKA,wBAAAsrB,GAEE,GAAIntB,KAAKutB,mBAAoB,OAE7B,MAAMC,EAAyB,OAE3BrQ,SAASsQ,mBACTtQ,SAASuQ,sBACTvQ,SAASwQ,yBACTxQ,SAASyQ,sBAGmB5tB,KAAKuiB,eAEjCviB,KAAKuiB,cAAe,EACpBviB,KAAK6E,QAAQ6W,UAAUwD,OAAO,oBAC9Blf,KAAKktB,yBACLltB,KAAK+E,KAAK,2BAKdoY,SAASsB,iBAAiB,mBAAoB+O,GAC9CrQ,SAASsB,iBAAiB,sBAAuB+O,GACjDrQ,SAASsB,iBAAiB,yBAA0B+O,GACpDrQ,SAASsB,iBAAiB,qBAAsB+O,GAGhDxtB,KAAKutB,mBAAqBC,CAC5B,CAKA,0BAAAK,GACM7tB,KAAKutB,qBACPpQ,SAAS2Q,oBAAoB,mBAAoB9tB,KAAKutB,oBACtDpQ,SAAS2Q,oBAAoB,sBAAuB9tB,KAAKutB,oBACzDpQ,SAAS2Q,oBAAoB,yBAA0B9tB,KAAKutB,oBAC5DpQ,SAAS2Q,oBAAoB,qBAAsB9tB,KAAKutB,oBACxDvtB,KAAKutB,mBAAqB,KAE9B,CAKA,OAAAjB,GACEtsB,KAAK6tB,6BACLtuB,MAAM+sB,SACR,CAKA,qBAAMyB,CAAgBnpB,EAAOC,SACrB7E,KAAKisB,SACb,CAKA,iBAAM+B,CAAYppB,EAAOC,GAEvB,GAAI7E,KAAKL,QAAQsuB,MAGf,OAFAjuB,KAAK+E,KAAK,YAAa,CAAEH,qBACnB5E,KAAKL,QAAQsuB,MAAMrpB,IAK3B5E,KAAK+E,KAAK,YAAa,CAAEH,UAEzB,MAAMhF,EAAaI,KAAKuqB,gBACxB,IAAK3qB,EAEH,YADAoF,QAAQgC,KAAK,kDAIf,IAAI6kB,EAAa7rB,KAAK6qB,iBAAiBjrB,GAEvC,GAAIisB,EAAY,CACd,MAAM1qB,EAAQ,IAAIvB,EACbisB,EAAW/pB,SACZ+pB,EAAa,CAAEhqB,MAAO,OAAO7B,KAAKwqB,iBAAkB1oB,OAAQ+pB,IAGhE,MAAMnlB,QAAe4kB,EAAAA,OAAO4C,SAAS,CACnC/sB,WACG0qB,KACA7rB,KAAK+qB,oBAAoBnrB,KAG9B,GAAI8G,EAAQ,CACN1G,KAAKL,QAAQwuB,yBACbznB,EAAOhB,MAAQ1F,KAAKkR,SAASkd,YAAYnuB,IAEzCD,KAAKL,QAAQ0uB,wBACb3nB,EAAO4nB,KAAOtuB,KAAKkR,SAASqd,WAAWtuB,IAEvCD,KAAKL,QAAQ6uB,iBACbtW,OAAOuW,OAAO/nB,EAAQ1G,KAAKL,QAAQ6uB,iBAEvC,MAAMxX,QAAa7V,EAAM0H,KAAKnC,GAC9B,IAAKsQ,GAAM1X,KAAKc,OAEZ,YADAkrB,EAAAA,OAAOprB,UAAU8W,GAAM1X,KAAKe,OAAS,qBAGrCL,KAAK2kB,YACP3kB,KAAK2kB,WAAW1Q,IAAI9S,SAEhBnB,KAAKisB,SACb,CACF,KAAO,CAGL,MAAM9qB,EAAQ,IAAIvB,EAEZ8G,QAAe4kB,EAAAA,OAAOC,WAAW,CACrC1pB,MAAO,OAAO7B,KAAKwqB,iBACnBiB,KAAM,IAAIM,EAAAA,SAAS,CACjB5qB,QACAW,OAAQ9B,KAAKL,QAAQqsB,YAAc,OAIvC,GAAItlB,EAAQ,CACV,MAAMsQ,QAAa7V,EAAM0H,KAAKnC,GAC9B,IAAKsQ,GAAM1X,KAAKc,OAEZ,YADAkrB,EAAAA,OAAOprB,UAAU8W,EAAK1X,KAAKe,OAAS,qBAGpCL,KAAK2kB,YACP3kB,KAAK2kB,WAAW1Q,IAAI9S,SAEhBnB,KAAKisB,SACb,CACF,CACF,CAKA,oBAAMyC,CAAe9pB,EAAOC,GAC1B,MAAM4V,EAAS5V,EAAQkX,aAAa,gBAAkB,OAEtD/b,KAAK+E,KAAK,eAAgB,CACxB0V,SACAkU,OAAQ3uB,KAAKqjB,aACbze,UAGwB,WAAtB5E,KAAKqjB,aACHrjB,KAAK2kB,iBACD3kB,KAAK2kB,WAAWiK,SAASnU,GAE/BzV,QAAQgC,KAAK,8DAIXhH,KAAKL,QAAQkvB,eACT7uB,KAAKL,QAAQkvB,SAAS7uB,KAAK2kB,YAAYmK,UAAY,GAAIrU,GAE7DzV,QAAQgC,KAAK,gEAGnB,CAKA,yBAAM+nB,CAAoBnqB,EAAOC,GAC/B,MAAMmqB,EAAanqB,EAAQiF,MAAM0X,OAE7BxhB,KAAK2kB,aACP3kB,KAAKivB,UAAU,SAAUD,GAGzBhvB,KAAK2kB,WAAWjkB,OAAOwuB,MAAQ,EAE3BlvB,KAAK2kB,WAAWwK,kBACZnvB,KAAK2kB,WAAWvV,QAGtBpP,KAAKsE,UAKTtE,KAAK8nB,oBAEL9nB,KAAK+E,KAAK,eAAgB,CAAEiqB,aAAYpqB,UACxC5E,KAAK+E,KAAK,iBACZ,CAKA,yBAAMslB,CAAoBzlB,EAAOC,GAE/B7E,KAAKivB,UAAU,SAAU,MAGrBjvB,KAAK2kB,aACP3kB,KAAK2kB,WAAWjkB,OAAOwuB,MAAQ,EAE3BlvB,KAAK2kB,WAAWwK,mBACZnvB,KAAK2kB,WAAWvV,eAKpBpP,KAAKsE,SACXtE,KAAK8nB,oBAEL9nB,KAAK+E,KAAK,eAAgB,CAAEiqB,WAAY,GAAIpqB,UAC5C5E,KAAK+E,KAAK,iBACZ,CAKA,SAAA6jB,GACE,MAAMxU,EAAOpU,KAAK2kB,YAAYjkB,QAAQ0T,KACtC,OAAKA,EACEA,EAAKgb,WAAW,KAAOhb,EAAK6M,MAAM,GAAK7M,EAD5B,IAEpB,CAKA,gBAAAyU,GACE,MAAMzU,EAAOpU,KAAK2kB,YAAYjkB,QAAQ0T,KACtC,OAAKA,GACEA,EAAKgb,WAAW,KAAO,OADZ,KAEpB,CAKA,WAAArG,CAAYsG,GACV,MAAkB,QAAdA,EACK,qDACgB,SAAdA,EACF,yDAEA,sDAEX,CAKA,kBAAMC,CAAa1qB,EAAOC,GACxBD,EAAM+Z,iBACN,MAAMoC,EAAQlc,EAAQkX,aAAa,cAC7BsT,EAAYxqB,EAAQkX,aAAa,kBAEvC,GAAI/b,KAAK2kB,WAAY,CACnB,IAAI4K,EAgBJ,GAbEA,EADgB,SAAdF,OACQ,EACa,SAAdA,EACC,IAAItO,IAEJA,EAGZ/gB,KAAK2kB,WAAW6K,UAAU,IACrBxvB,KAAK2kB,WAAWjkB,OACnB0T,KAAMmb,EACNL,MAAO,IAGLlvB,KAAK2kB,WAAWwK,kBACZnvB,KAAK2kB,WAAWvV,YACjB,CAEL,GAAImgB,EAAS,CACX,MAAME,EAAOF,EAAQH,WAAW,KAC1BM,EAAYD,EAAOF,EAAQtO,MAAM,GAAKsO,EAE5CvvB,KAAK2kB,WAAWvQ,KAAK,CAACub,EAAGC,KACvB,MAAMC,EAAOF,EAAErlB,IAAIolB,GACbI,EAAOF,EAAEtlB,IAAIolB,GAEnB,OAAIG,EAAOC,EAAaL,EAAO,GAAI,EAC/BI,EAAOC,EAAaL,GAAO,EAAK,EAC7B,GAEX,CAEAzvB,KAAKsE,QACP,CACF,CAGAtE,KAAK+vB,kBAEL/vB,KAAK+E,KAAK,aAAc,CAAEgc,QAAOnc,UACjC5E,KAAK+E,KAAK,iBACZ,CAKA,eAAAgrB,GACE,IAAK/vB,KAAK6E,QAAS,OAEnB,MAAMmrB,EAAmBhwB,KAAK4oB,YACxBqH,EAAiBjwB,KAAK6oB,mBAG5B7oB,KAAKyC,QAAQuR,QAAQ6F,IACnB,GAAI7Z,KAAKyiB,WAAgC,IAApB5I,EAAO4I,SAAoB,CAE9C,MAAMuC,SAAEA,GAAahlB,KAAK+kB,eAAelL,EAAOxB,KAE1C6X,EAAWlwB,KAAK6E,QAAQsW,cAAc,4CAA4C6J,OACxF,GAAIkL,EAAU,CACZ,MAAMC,EAAWH,IAAqBhL,EAChC8D,EAAW9oB,KAAK+oB,YAAYoH,EAAWF,EAAiB,MAC9DC,EAAS1U,UAAYsN,EAGrB,MAAMsH,EAAeF,EAASG,mBAC9B,GAAID,EAAc,CAChB,MAAME,EAAUF,EAAajV,cAAc,gBAAgB6J,6BACrDuL,EAAWH,EAAajV,cAAc,gBAAgB6J,8BACtDwL,EAAWJ,EAAajV,cAAc,gBAAgB6J,8BAExDsL,GACFA,EAAQ5U,UAAU+U,OAAO,SAAUN,GAA+B,QAAnBF,GAE7CM,GACFA,EAAS7U,UAAU+U,OAAO,SAAUN,GAA+B,SAAnBF,GAE9CO,GACFA,EAAS9U,UAAU+U,OAAO,UAAWN,GAAYH,IAAqBhL,EAE1E,CACF,CACF,GAEJ,CAKA,uBAAM0L,CAAkB9rB,EAAOC,GAC7BD,EAAMiX,kBACN,MAAM8U,EAAyB3wB,KAAKupB,UAAU1pB,KAAO,GACnDqU,MAAMC,KAAKnU,KAAKupB,UAAUjI,UAAUsP,MAAMC,GAAQA,EAAKpV,UAEpDkV,EASH3wB,KAAK8wB,iBAPL9wB,KAAK+wB,YAAYlO,IACVA,EAASpH,UACZoH,EAASrF,WASf,MAAMwT,EAAgBhxB,KAAK6E,SAASsW,cAAc,yBAC9C6V,GACFA,EAActV,UAAU+U,OAAO,YAAaE,GAI9C3wB,KAAKypB,yBACP,CAKA,oBAAMwH,GAEJjxB,KAAKkxB,YAAclxB,KAAK0nB,mBAAmBY,QAAU,GACrDtoB,KAAKmxB,aAAenxB,KAAKklB,uBAC3B,CAKA,mBAAMjK,GASJ,SARM1b,MAAM0b,gBAGRjb,KAAKwkB,iBACPxkB,KAAK6kB,qBAIH7kB,KAAK2iB,WAAa3iB,KAAK2kB,WAAY,CACrC,MAAM/gB,EAAQ5D,KAAK2kB,WAAW9Y,MAAMulB,OAASpxB,KAAK2kB,WAAW7R,SACvDoc,EAAQlvB,KAAK2kB,WAAWjkB,QAAQwuB,OAAS,EACzCrvB,EAAOG,KAAK2kB,WAAWjkB,QAAQb,MAAQ,GACvCwxB,EAAMppB,KAAK8J,IAAImd,EAAQrvB,EAAM+D,GAE7B0tB,EAAUtxB,KAAK6E,QAAQsW,cAAc,wBACrCoW,EAAQvxB,KAAK6E,QAAQsW,cAAc,sBACnCqW,EAAUxxB,KAAK6E,QAAQsW,cAAc,wBAEvCmW,IAASA,EAAQhS,YAAc4P,EAAQ,GACvCqC,MAAajS,YAAc+R,GAC3BG,MAAiBlS,YAAc1b,GAGnC,MAAM6tB,EAAiBzxB,KAAK6E,QAAQsW,cAAc,oCAC9CsW,IACFA,EAAe3nB,MAAQjK,GAIzBG,KAAK0xB,kBACP,CAGA1xB,KAAK+vB,kBAGL/vB,KAAK8nB,oBAGL9nB,KAAKoqB,0BACP,CAOA,gBAAAsH,GACE,MAAMC,EAAsB3xB,KAAK6E,QAAQsW,cAAc,iCACvD,IAAKwW,IAAwB3xB,KAAK2kB,WAAY,OAE9C,MAAM/gB,EAAQ5D,KAAK2kB,WAAW9Y,MAAMulB,OAASpxB,KAAK2kB,WAAW7R,SACvDjT,EAAOG,KAAK2kB,WAAWjkB,QAAQb,MAAQ,GACvCqvB,EAAQlvB,KAAK2kB,WAAWjkB,QAAQwuB,OAAS,EACzC0C,EAAc3pB,KAAKoK,MAAM6c,EAAQrvB,GAAQ,EACzCgyB,EAAa5pB,KAAK6pB,KAAKluB,EAAQ/D,GAErC,GAAIgyB,GAAc,EAEhB,YADAF,EAAoBnW,UAAY,IAIlC,MAAMuW,EAAWH,EAAc,EAAIA,EAAc,EAAIC,EAC/CG,EAAWJ,EAAcC,EAAaD,EAAc,EAAI,EAExDK,EAAQ,GAGdA,EAAMvY,KAAK,uGAEuDqY,sFAOlE,MACMG,iBAAa,IAAIne,IAAI,CAAC,EAAG8d,IAC/B,IAAA,IAASM,EAAIP,EAFK,EAEoBO,GAAKP,EAFzB,EAEkDO,IAC9DA,GAAK,GAAKA,GAAKN,GAAYK,EAAWje,IAAIke,GAEhD,MAAMC,EAAUle,MAAMC,KAAK+d,GAAY9d,KAAK,CAACub,EAAGC,IAAMD,EAAIC,GAG1D,IAAIyC,EAAO,EACX,IAAA,MAAWjd,KAAKgd,EACVC,GAAQjd,EAAIid,EAAO,GAErBJ,EAAMvY,KAAK,8FAIbuY,EAAMvY,KAAK,kCACctE,IAAMwc,EAAc,SAAW,+EACUxc,MAAMA,gCAGxEid,EAAOjd,EAIT6c,EAAMvY,KAAK,uGAEuDsY,uFAMlEL,EAAoBnW,UAAYyW,EAAMzY,KAAK,GAC7C,CAMA,kBAAM8Y,CAAa1tB,EAAOC,GACxBD,EAAM+Z,iBAEN,MAAM4T,EAAUC,SAAS3tB,EAAQkX,aAAa,aAAc,IACtDlc,EAAOG,KAAK2kB,WAAWjkB,QAAQb,MAAQ,GACvC+D,EAAQ5D,KAAK2kB,WAAW9Y,MAAMulB,OAASpxB,KAAK2kB,WAAW7R,SACvD+e,EAAa5pB,KAAKwqB,IAAI,EAAGxqB,KAAK6pB,KAAKluB,EAAQ/D,IAEjD,IAAI6yB,EAAOC,MAAMJ,GAAW,EAAIA,EAC5BG,EAAO,IAAGA,EAAOb,GACjBa,EAAOb,IAAYa,EAAO,GAE9B1yB,KAAK2kB,WAAW6K,UAAU,IACrBxvB,KAAK2kB,WAAWjkB,OACnBwuB,OAAQwD,EAAO,GAAK7yB,IAGlBG,KAAK2kB,WAAWwK,kBACZnvB,KAAK2kB,WAAWvV,QAEtBpP,KAAKsE,SAGPtE,KAAK+E,KAAK,aAAc,CAAE2tB,OAAM9tB,UAChC5E,KAAK+E,KAAK,iBACZ,CAKA,sBAAM6tB,CAAiBhuB,EAAOC,GAC5B,MAAMguB,EAAUL,SAAS3tB,EAAQiF,OAE7B9J,KAAK2kB,aAEP3kB,KAAK2kB,WAAW6K,UAAU,IACrBxvB,KAAK2kB,WAAWjkB,OACnBwuB,MAAO,EACPrvB,KAAMgzB,IAGJ7yB,KAAK2kB,WAAWwK,mBACZnvB,KAAK2kB,WAAWvV,QAExBpP,KAAKsE,UAGPtE,KAAK+E,KAAK,iBAAkB,CAAElF,KAAMgzB,EAASjuB,UAC7C5E,KAAK+E,KAAK,iBACZ,CAKA,gBAAA2iB,GACE,IAAK1nB,KAAK2kB,YAAYjkB,OACpB,MAAO,CAAA,EAET,MAAMwuB,MAAEA,OAAOrvB,EAAAuU,KAAMA,KAAS0e,GAAc9yB,KAAK2kB,WAAWjkB,OACtD4iB,EAAU,CAAA,EAGVyP,qBAAoBhf,IAgC1B,OA7ByB/T,KAAKwnB,yBACbxT,QAAQgf,IACvB,GAA8B,cAA1BA,EAAUnL,OAAO9lB,KAAsB,CACzC,MAAMsW,EAAM2a,EAAU3a,IAChB4a,EAAYD,EAAUnL,OAAOoL,WAAa,WAC1CC,EAAUF,EAAUnL,OAAOqL,SAAW,SACtCC,EAAYH,EAAUnL,OAAOsL,WAAa,WAG5CL,EAAUK,KAAe9a,IAAQya,EAAUG,IAAcH,EAAUI,MACrE5P,EAAQjL,GAAO,CACb6W,MAAO4D,EAAUG,IAAc,GAC/B5B,IAAKyB,EAAUI,IAAY,IAG7BH,EAAc9e,IAAIgf,GAClBF,EAAc9e,IAAIif,GAClBH,EAAc9e,IAAIkf,GAEtB,IAIFjb,OAAO6J,KAAK+Q,GAAW9e,QAAQ8M,IACxBiS,EAAc7W,IAAI4E,KACrBwC,EAAQxC,GAAYgS,EAAUhS,MAI3BwC,CACT,CAKA,SAAA2L,CAAU5W,EAAKvO,GACb,IAAK9J,KAAK2kB,WAAY,OAEtB,MAAMyO,EAAepzB,KAAKqzB,gBAAgBhb,GAG1C,GAAI+a,GAAsC,cAAtBA,EAAarxB,KAAsB,CACrD,MAAMkxB,EAAYG,EAAaH,WAAa,WACtCC,EAAUE,EAAaF,SAAW,SAClCC,EAAYC,EAAaD,WAAa,kBAGrCnzB,KAAK2kB,WAAWjkB,OAAOuyB,UACvBjzB,KAAK2kB,WAAWjkB,OAAOwyB,UACvBlzB,KAAK2kB,WAAWjkB,OAAOyyB,GAG1BrpB,GAA0B,iBAAVA,IAAuBA,EAAMolB,OAASplB,EAAMunB,OAC1DvnB,EAAMolB,QAAOlvB,KAAK2kB,WAAWjkB,OAAOuyB,GAAanpB,EAAMolB,OACvDplB,EAAMunB,MAAKrxB,KAAK2kB,WAAWjkB,OAAOwyB,GAAWppB,EAAMunB,KACvDrxB,KAAK2kB,WAAWjkB,OAAOyyB,GAAa9a,EAExC,KAAO,CAEL,MAAM0I,MAAEA,EAAAjK,OAAOA,GAAW+J,EAAexI,GAOzC,UAJOrY,KAAK2kB,WAAWjkB,OAAO2X,UACvBrY,KAAK2kB,WAAWjkB,OAAOqgB,UACvB/gB,KAAK2kB,WAAWjkB,OAAO,GAAGqgB,UAE5BjX,GAAUoK,MAAMgB,QAAQpL,IAA2B,IAAjBA,EAAMgJ,OAC3C,OAIEoB,MAAMgB,QAAQpL,GACK,IAAjBA,EAAMgJ,OAER9S,KAAK2kB,WAAWjkB,OAAOqgB,GAASjX,EAAM,GAGtC9J,KAAK2kB,WAAWjkB,OAAO,GAAGqgB,SAAejX,EAAM0P,KAAK,KAItDxZ,KAAK2kB,WAAWjkB,OAAO2X,GAAOvO,CAElC,CACF,CAKA,sBAAA0d,GACE,MAAMlE,EAAU,GA2BhB,OAxBAtjB,KAAKyC,QAAQuR,QAAQ6F,IACnB,GAAIA,EAAOjG,OAAQ,CACjB,MAAMoR,SAAEA,GAAahlB,KAAK+kB,eAAelL,EAAOxB,KAChDiL,EAAQ5J,KAAK,CACXrB,IAAK2M,EACL/iB,MAAO4X,EAAOjG,OAAO3R,OAAS4X,EAAO5X,OAAS+iB,EAC9CjjB,KAAM8X,EAAOjG,OAAO7R,KACpB8lB,OAAQhO,EAAOjG,QAEnB,IAIE5T,KAAKujB,mBAAqBrP,MAAMgB,QAAQlV,KAAKujB,oBAC/CvjB,KAAKujB,kBAAkBvP,QAAQJ,IAC7B0P,EAAQ5J,KAAK,CACXrB,IAAKzE,EAAO5R,MAAQ4R,EAAOyE,IAC3BpW,MAAO2R,EAAO3R,MACdF,KAAM6R,EAAO7R,KACb8lB,OAAQjU,MAKP0P,CACT,CAKA,eAAA+P,CAAgBC,GAEd,MAAMzZ,EAAS7Z,KAAKyC,QAAQuZ,KAAKC,IAC/B,MAAM+I,SAAEA,GAAahlB,KAAK+kB,eAAe9I,EAAI5D,KAC7C,OAAO2M,IAAasO,IAEtB,GAAIzZ,GAAUA,EAAOjG,OACnB,OAAOiG,EAAOjG,OAIhB,GAAI5T,KAAKujB,mBAAqBrP,MAAMgB,QAAQlV,KAAKujB,mBAAoB,CACnE,MAAM3P,EAAS5T,KAAKujB,kBAAkBvH,SAAWuX,EAAEvxB,MAAQuxB,EAAElb,OAASib,GACtE,GAAI1f,EACF,OAAOA,CAEX,CAEA,OAAO,IACT,CAKA,cAAA6U,CAAepQ,GACb,GAAY,WAARA,EAAkB,MAAO,SAE7B,MAAMzE,EAAS5T,KAAKsjB,QAAQjL,GAC5B,GAAIzE,GAAUA,EAAO3R,MAAO,OAAO2R,EAAO3R,MAE1C,MAAMuxB,EAAmBxzB,KAAKujB,kBAAkBvH,KAAKuX,IAClDA,EAAEvxB,MAAQuxB,EAAElb,OAASA,GAExB,OAAImb,GAAoBA,EAAiBvxB,MAAcuxB,EAAiBvxB,MAEjEoW,EAAIyM,OAAO,GAAG/jB,cAAgBsX,EAAI4I,MAAM,EACjD,CAKA,qBAAAwS,CAAsBpb,EAAKvO,GACzB,GAAY,WAARuO,EAAkB,MAAO,IAAIvO,KAEjC,MAAM8J,EAAS5T,KAAKsjB,QAAQjL,IACdrY,KAAKujB,kBAAkBvH,KAAKuX,IAAMA,EAAEvxB,MAAQuxB,EAAElb,OAASA,GAErE,GAAIzE,GAA0B,cAAhBA,EAAO7R,MAAyC,iBAAV+H,EAGlD,MAAO,GAFOA,EAAMolB,OAAS,SACjBplB,EAAMunB,KAAO,KAI3B,GAAIzd,GAA0B,WAAhBA,EAAO7R,MAAqB6R,EAAOjU,QAAS,CACxD,GAAiC,iBAAtBiU,EAAOjU,QAAQ,GAAiB,CACzC,MAAM0e,EAASzK,EAAOjU,QAAQqc,KAAK6K,GAAOA,EAAI/c,QAAUA,GACxD,OAAOuU,EAASA,EAAOpc,MAAQ6H,CACjC,CACA,OAAOA,CACT,CAEA,OAAOA,CACT,CAKA,aAAA8d,CAAc7lB,GASZ,MARc,CACZgI,KAAQ,SACRyT,OAAU,SACVkW,KAAQ,WACRC,UAAa,iBACbC,OAAU,MACVC,QAAW,aAEA9xB,IAAS,QACxB,CAKA,uBAAM+xB,CAAkBlvB,EAAOC,GAC7B,MAAMyuB,EAAYzuB,EAAQkX,aAAa,mBACjCqX,EAAepzB,KAAKqzB,gBAAgBC,GACpC1W,EAAe5c,KAAK0nB,mBAAmB4L,GAE7C,IAAKF,EAEH,YADApuB,QAAQgC,KAAK,kCAAmCssB,GAOlD,MAAM5sB,QAAe4kB,EAAAA,OAAO4C,SAAS,CACnCrsB,MAAO,QAAoB,IAAjB+a,GAA+C,KAAjBA,EAAsB,OAAS,SAAS5c,KAAKyoB,eAAe6K,YACpGzzB,KAAM,KACNiC,OAAQ,CAAC9B,KAAK+zB,uBAAuBX,EAAcxW,EAAc0W,MAGnE,GAAI5sB,EAAQ,CAEV,MAAMstB,EAAiBh0B,KAAKi0B,mBAAmBb,EAAc1sB,GAE7D1G,KAAKivB,UAAUqE,EAAWU,SACpBh0B,KAAKk0B,cACb,CACF,CAKA,sBAAAH,CAAuBX,EAAcxW,EAAc0W,GACjD,MAAMvS,EAAQ,CACZ/e,KAAM,eACNC,MAAOmxB,EAAanxB,MACpB6H,MAAO8S,KACJwW,EAEH/wB,YAAa+wB,EAAa/wB,aAAe+wB,EAAae,aAIxD,GAA0B,cAAtBf,EAAarxB,KAEfgf,EAAMkS,UAAYlS,EAAMkS,WAAa,WACrClS,EAAMmS,QAAUnS,EAAMmS,SAAW,SACjCnS,EAAMoS,UAAYpS,EAAMoS,WAAa,WACrCpS,EAAMtG,OAASsG,EAAMtG,QAAU,aAC/BsG,EAAMqT,cAAgBrT,EAAMqT,eAAiB,eAC7CrT,EAAMlG,UAAYkG,EAAMlG,WAAa,OACrCkG,EAAM9e,MAAQ8e,EAAM9e,OAAS,aAGzB2a,GAAwC,iBAAjBA,IACzBmE,EAAMsT,UAAYzX,EAAasS,OAAS,GACxCnO,EAAMuT,QAAU1X,EAAayU,KAAO,SAExC,GAAiC,gBAAtB+B,EAAarxB,KAAwB,CAE9C,IAAIwyB,EAAa,GACb3X,IACE1I,MAAMgB,QAAQ0H,GAChB2X,EAAa3X,EACoB,iBAAjBA,IAEhB2X,EAAa3X,EAAa/J,MAAM,KAAKuF,IAAImJ,GAAKA,EAAEC,QAAQ5N,OAAO2N,GAAKA,KAIxER,EAAMjX,MAAQyqB,EAGTxT,EAAM1e,aAAgB0e,EAAMoT,cAC3Bf,EAAa/wB,aAAe+wB,EAAae,YAC3CpT,EAAM1e,YAAc+wB,EAAa/wB,aAAe+wB,EAAae,YACpDf,EAAanxB,QACtB8e,EAAM1e,YAAc,UAAU+wB,EAAanxB,YAGjD,CAEA,OAAO8e,CACT,CAKA,kBAAAkT,CAAmBb,EAAcoB,GAC/B,GAA0B,cAAtBpB,EAAarxB,KAAsB,CAErC,MAAMkxB,EAAYG,EAAaH,WAAa,WACtCC,EAAUE,EAAaF,SAAW,SAOxC,MALe,CACbhE,MAAOsF,EAAWvB,GAClB5B,IAAKmD,EAAWtB,GAIpB,CAEA,OAAIE,EAAarxB,KAERyyB,EAAWC,YAItB,CAKA,kBAAMP,GAOJ,GALIl0B,KAAK2kB,aACP3kB,KAAK2kB,WAAWjkB,OAAOwuB,MAAQ,GAI7BlvB,KAAK2kB,YAAYwK,YACnB,UACQnvB,KAAK2kB,WAAWvV,QACtBpP,KAAKsE,QACP,OAASjE,GACP2E,QAAQ3E,MAAM,iCAAkCA,GAChDL,KAAKsE,QACP,MAEAtE,KAAKsE,SAIPtE,KAAK8nB,oBAGL9nB,KAAK+E,KAAK,iBACZ,CAKA,wBAAM2vB,CAAmB9vB,EAAOC,GAC9B,MAAMyuB,EAAYzuB,EAAQkX,aAAa,gBAGjCgF,MAAEA,GAAUF,EAAeyS,GAGjC,IAAIF,EAAepzB,KAAKqzB,gBAAgBtS,IAAU/gB,KAAKqzB,gBAAgBC,GAGvE,MAAM7L,EAAgBznB,KAAK0nB,mBACrB9K,EAAe6K,EAAc6L,IAAc7L,EAAc1G,GAE/D,IAAKqS,EAEH,YADApuB,QAAQgC,KAAK,kCAAmCssB,EAAW,YAAavS,GAO1E,MAAMra,QAAe4kB,EAAAA,OAAO4C,SAAS,CACnCrsB,MAAO,QAAQ7B,KAAKyoB,eAAe1H,YACnClhB,KAAM,KACNP,KAAM,CAACm1B,aAAc7X,GACrB9a,OAAQ,CAAC9B,KAAK+zB,uBAAuBX,EAAcxW,EAAcmE,MAGnE,GAAIra,EAAQ,CAEV,MAAMstB,EAAiBh0B,KAAKi0B,mBAAmBb,EAAc1sB,GAC7D1G,KAAKivB,UAAUqE,EAAWU,SACpBh0B,KAAKk0B,cACb,CACF,CAKA,0BAAMS,CAAqB/vB,EAAOC,GAChC,MAAMyuB,EAAYzuB,EAAQkX,aAAa,gBAGjCgF,MAAEA,GAAUF,EAAeyS,GAGjCtzB,KAAKivB,UAAUqE,EAAW,MAGR,WAAdA,GACFtzB,KAAKkoB,mBAAmB,IAGtBloB,KAAK2kB,WAAWwK,mBACZnvB,KAAK2kB,WAAWvV,QAExBpP,KAAKsE,SAGLtE,KAAK8nB,oBAEL9nB,KAAK+E,KAAK,gBAAiB,CAAEsT,IAAKib,EAAWvS,UAC7C/gB,KAAK+E,KAAK,iBACZ,CAKA,6BAAM6vB,CAAwBhwB,EAAOC,GACnC,IAAK7E,KAAK2kB,WAAY,OAGtB,MAAMuK,MAAEA,EAAArvB,KAAOA,EAAAuU,KAAMA,GAASpU,KAAK2kB,WAAWjkB,OAC9CV,KAAK2kB,WAAWjkB,OAAS,CAAEwuB,QAAOrvB,QAC9BuU,IAAMpU,KAAK2kB,WAAWjkB,OAAO0T,KAAOA,GAGxCpU,KAAKkoB,mBAAmB,IAEpBloB,KAAK2kB,WAAWwK,mBACZnvB,KAAK2kB,WAAWvV,QAExBpP,KAAKsE,SAGLtE,KAAK8nB,oBAEL9nB,KAAK+E,KAAK,iBACV/E,KAAK+E,KAAK,iBACZ,CAKA,uBAAA0kB,GACE,IAAKzpB,KAAKgZ,cAA6C,IAA7BhZ,KAAKgZ,aAAalG,OAAc,OAE1D,MAAM+hB,EAAgB70B,KAAK80B,mBAAmBhiB,OAE9C,GAA8B,QAA1B9S,KAAK0jB,iBAA4B,CAEnC,MAAMqR,EAAQ/0B,KAAK6E,SAASsW,cAAc,4BACpC6Z,EAAUh1B,KAAK6E,SAASsW,cAAc,uBAExC4Z,GAASC,IACXA,EAAQ1V,YAAcuV,EAGlBA,EAAgB,EAClBE,EAAMrZ,UAAUwD,OAAO,UAEvB6V,EAAMrZ,UAAUzH,IAAI,UAG1B,KAAO,CAEL,MAAM8gB,EAAQ/0B,KAAK6E,SAASsW,cAAc,wBACpC6Z,EAAUh1B,KAAK6E,SAASsW,cAAc,uBAExC4Z,GAASC,IACXA,EAAQ1V,YAAcuV,EACtBE,EAAM/X,MAAMC,QAAU4X,EAAgB,EAAI,QAAU,OAExD,CAGA,MAAM7D,EAAgBhxB,KAAK6E,SAASsW,cAAc,yBAClD,GAAI6V,EAAe,CACjB,MAAMiE,EAAcj1B,KAAKupB,UAAU1pB,KAAO,GACxCqU,MAAMC,KAAKnU,KAAKupB,UAAUjI,UAAUsP,MAAMC,GAAQA,EAAKpV,UACnDyZ,EAAehhB,MAAMC,KAAKnU,KAAKupB,UAAUjI,UAAUnM,KAAK0b,GAAQA,EAAKpV,UAE3EuV,EAActV,UAAU+U,OAAO,WAAYwE,GAC3CjE,EAActV,UAAU+U,OAAO,iBAAkBwE,GAAeC,GAGhE,MAAMxa,EAAOsW,EAAc7V,cAAc,KACrCT,IACFA,EAAK9B,WAAaqc,GAAeC,EAAe,aAAe,cAEnE,CACF,CAKA,mBAAMC,CAAcvwB,EAAOC,GACzB,MAAMuwB,EAAcvwB,EAAQkX,aAAa,eAAe2O,QAAQ,SAAU,IACpE2K,EAAgBr1B,KAAK80B,mBAE3B90B,KAAK+E,KAAK,eAAgB,CACxBJ,OAAQywB,EACRE,MAAOD,EACPzwB,SAEJ,CAKA,4BAAM2wB,CAAuB3wB,EAAOC,GAClC7E,KAAK8wB,iBACL9wB,KAAKypB,yBACP,CAKA,iCAAM+L,CAA4B5wB,EAAOC,GACvC,MAAM4wB,EAAcjD,SAAS3tB,EAAQkX,aAAa,qBAAsB,IAClE+K,EAAS9mB,KAAK4jB,eAAe6R,GAE/B3O,GAAoC,mBAAnBA,EAAOC,eACpBD,EAAOC,QAAQ2O,KAAK11B,KAAM4E,EAAOC,EAE3C,ECl5EF,MAAM8wB,kBAAkBC,EAAAA,KACtB,WAAAv2B,CAAYM,EAAU,IACpBJ,MAAM,IACDI,EACHk2B,SAAUl2B,EAAQk2B,UAAYl2B,EAAQqC,MAAQ,UAIhDhC,KAAK6B,MAAQlC,EAAQkC,OAAS7B,KAAK61B,SACnC71B,KAAK2F,YAAchG,EAAQgG,aAAe,GAG1C3F,KAAKN,WAAaC,EAAQD,YAAc,KACxCM,KAAK2kB,WAAahlB,EAAQglB,YAAc,KAGxC3kB,KAAK81B,aAAen2B,EAAQm2B,cAAgB,CAAA,EAG5C91B,KAAK+1B,WAAap2B,EAAQo2B,YAAc,QAIxC/1B,KAAKg2B,gBAAkB,CAErBvzB,QAAS9C,EAAQ8C,SAAW,GAC5BqW,QAASnZ,EAAQmZ,SAAW,KAC5BC,YAAapZ,EAAQoZ,aAAe,KACpCC,aAAcrZ,EAAQqZ,cAAgB,KACtC0K,iBAAkB/jB,EAAQ+jB,kBAAoB,MAC9Cd,YAAajjB,EAAQijB,aAAe,OAEpCE,QAASnjB,EAAQmjB,SAAWnjB,EAAQqsB,YAAcrsB,EAAQs2B,WAC1DlT,SAAUpjB,EAAQojB,UAAYpjB,EAAQu2B,UAAYv2B,EAAQqsB,WAG1DnJ,SAAUljB,EAAQkjB,SAClBG,eAAgBrjB,EAAQqjB,eACxBC,iBAAkBtjB,EAAQsjB,iBAC1BC,kBAAmBvjB,EAAQujB,kBAG3BV,YAAmC,IAAvB7iB,EAAQ6iB,WACpBC,UAA+B,IAArB9iB,EAAQ8iB,SAClBC,YAAmC,IAAvB/iB,EAAQ+iB,WACpBC,WAAiC,IAAtBhjB,EAAQgjB,UAGnBR,cAAexiB,EAAQwiB,gBAAkBxiB,EAAQyiB,WAAa,WAAa,QAG3EkB,QAAS3jB,EAAQ2jB,SAAW3jB,EAAQ4jB,mBAAqB,GACzDC,gBAAiB7jB,EAAQ6jB,kBAAmB,EAC5CC,oBAAqB9jB,EAAQ8jB,qBAAuB,GACpDS,gBAAiBvkB,EAAQukB,iBAAmB,UAG5CL,aAAc,CACZC,SAAS,EACTC,UAAU,EACVC,OAAO,EACPC,YAAY,KACTtkB,EAAQkkB,cAIbxB,aAAc1iB,EAAQ0iB,cAAgB,oBACtC8B,kBAAmBxkB,EAAQwkB,mBAAqB,YAChDwC,SAA6B,IAApBhnB,EAAQgnB,QACjBvD,YAAmC,IAAvBzjB,EAAQyjB,WAGpB+H,WAAYxrB,EAAQwrB,WACpBS,WAAYjsB,EAAQisB,WACpBM,aAAcvsB,EAAQusB,aACtB+B,MAAOtuB,EAAQsuB,MACfY,SAAUlvB,EAAQkvB,YAGflvB,EAAQw2B,kBAIbn2B,KAAKo2B,gBAA4C,IAA3Bz2B,EAAQy2B,eAG9Bp2B,KAAKq2B,YAAc,KACnBr2B,KAAKs2B,WAAY,EAGjBt2B,KAAKmD,SAAWxD,EAAQwD,UAAYnD,KAAKu2B,eAC3C,CAKA,aAAAA,GACE,MAAO,mzBAwBT,CAKA,YAAMC,SACEj3B,MAAMi3B,SAGPx2B,KAAK2kB,aACJ3kB,KAAKN,WACPM,KAAK2kB,WAAa,IAAI3kB,KAAKN,WAE3BM,KAAK2kB,WAAa,IAAIjlB,cAK1BM,KAAKy2B,yBAGLz2B,KAAKiZ,UAAY,IAAIgJ,UAAU,CAC7B0C,WAAY3kB,KAAK2kB,WACjB2E,YAAa,QACboN,cAAc,KACX12B,KAAKg2B,kBAIVh2B,KAAK22B,SAAS32B,KAAKiZ,WAGnBjZ,KAAK42B,qBACP,CAKA,mBAAAA,GAEM52B,KAAKo2B,gBAAkBp2B,KAAK2kB,aAE9B3kB,KAAK2kB,WAAWC,GAAG,cAAe,KAChC5kB,KAAKs2B,WAAY,IAGnBt2B,KAAK2kB,WAAWC,GAAG,YAAa,KAC9B5kB,KAAKs2B,WAAY,EACjBt2B,KAAKq2B,4BAAA,IAAkBvnB,MAAO+nB,qBAC9B72B,KAAK82B,yBAKT92B,KAAKiZ,UAAU2L,GAAG,iBAAkB,KAC9B5kB,KAAKo2B,gBACPp2B,KAAK+2B,YAkBT/2B,KAAKiZ,UAAU2L,GAAG,cAAenjB,OAAS4W,gBAClCrY,KAAKg3B,iBAAiB3e,KAI9BrY,KAAKiZ,UAAU2L,GAAG,WAAYnjB,OAASN,YACjCnB,KAAKmrB,kBACDnrB,KAAKmrB,WAAWhqB,KAI1BnB,KAAKiZ,UAAU2L,GAAG,WAAYnjB,OAASN,YACjCnB,KAAK4rB,kBACD5rB,KAAK4rB,WAAWzqB,KAI1BnB,KAAKiZ,UAAU2L,GAAG,aAAcnjB,OAASN,YACnCnB,KAAKksB,oBACDlsB,KAAKksB,aAAa/qB,KAO5BnB,KAAKiZ,UAAU2L,GAAG,YAAanjB,OAASmD,eAMxC5E,KAAKiZ,UAAU2L,GAAG,eAAgBnjB,OAASnC,WACrCU,KAAKg2B,gBAAgBnH,gBACjB7uB,KAAKg2B,gBAAgBnH,SAASvvB,IAG1C,CAKA,sBAAAm3B,GACE,MAAM/1B,EAAS,CAAA,EACTu2B,EAAQ,IAAKj3B,KAAK81B,gBAAiB91B,KAAKi3B,OAC9C,IAAKA,GAAuC,IAA9B/e,OAAO6J,KAAKkV,GAAOnkB,OAC7B,YAGgB,IAAhBmkB,EAAM/H,QAAqBxuB,EAAOwuB,MAAQsD,SAASyE,EAAM/H,QAAU,QACpD,IAAf+H,EAAMp3B,OAAoBa,EAAOb,KAAO2yB,SAASyE,EAAMp3B,OAAS,SAGjD,IAAfo3B,EAAM7iB,OAAoB1T,EAAO0T,KAAO6iB,EAAM7iB,WAG7B,IAAjB6iB,EAAM3O,SAAsB5nB,EAAO4nB,OAAS2O,EAAM3O,QAGtD,MAAM4O,EAAiB,CAAC,QAAS,OAAQ,OAAQ,SAAU,QAC3Dhf,OAAOC,QAAQ8e,GAAOjjB,QAAQ,EAAEqE,EAAKvO,MACnC,IAAKotB,EAAetpB,SAASyK,SAAkB,IAAVvO,GAAiC,KAAVA,EAE1D,GAAqB,iBAAVA,IAAuBA,EAAMslB,WAAW,MAAQtlB,EAAMslB,WAAW,MAC1E,IACE1uB,EAAO2X,GAAO8e,KAAKC,MAAMttB,EAC3B,OAAS4U,GACPhe,EAAO2X,GAAOvO,CAChB,MAEApJ,EAAO2X,GAAOvO,IAMhBoO,OAAO6J,KAAKrhB,GAAQoS,OAAS,GAC/B9S,KAAK2kB,WAAW6K,UAAU,IACrBxvB,KAAK2kB,WAAWjkB,UAChBA,GAGT,CAKA,OAAAq2B,CAAQM,GAAQ,GACd,IAAKr3B,KAAKo2B,iBAAmBp2B,KAAK2kB,aAAe3kB,KAAKkR,UAAUomB,OAC9D,OAIF,MAAMC,EAAa,IAAIC,IAAInjB,OAAOojB,UAC5BC,EAAgB,CAAA,EACtB,IAAA,MAAYrf,EAAKvO,KAAUytB,EAAWI,aACxB,SAARtf,IACFqf,EAAcrf,GAAOvO,GAKzB,MAAM8tB,EAAgB,CAAA,EAChBC,EAAmB73B,KAAK2kB,WAAWjkB,QAAU,CAAA,EAG/Cm3B,EAAiB3I,QACnB0I,EAAc1I,MAAQ2I,EAAiB3I,OAErC2I,EAAiBh4B,OACnB+3B,EAAc/3B,KAAOg4B,EAAiBh4B,MAEpCg4B,EAAiBzjB,OACnBwjB,EAAcxjB,KAAOyjB,EAAiBzjB,MAEpCyjB,EAAiBvP,SACnBsP,EAActP,OAASuP,EAAiBvP,QAI1CpQ,OAAOC,QAAQ0f,GAAkB7jB,QAAQ,EAAEqE,EAAKvO,MACzC,CAAC,QAAS,OAAQ,OAAQ,UAAU8D,SAASyK,SAAkB,IAAVvO,GAAiC,KAAVA,IAG7E8tB,EAAcvf,GADK,iBAAVvO,EACYqtB,KAAKW,UAAUhuB,GAEfA,KAM3B,MAAMiuB,EACJ7f,OAAO6J,KAAK6V,GAAeziB,KAAKkD,GAC9BgJ,OAAOqW,EAAcrf,IAAQ,MAAQgJ,OAAOuW,EAAcvf,IAAQ,MAEpEH,OAAO6J,KAAK2V,GAAeviB,KAAKkD,KAC5BA,KAAOuf,IAGb53B,KAAKi3B,MAAQW,GACRG,GAAeV,IAGpBr3B,KAAKg4B,iBAAiBJ,GAAe,GAAM,EAC7C,CAKA,mBAAAd,GACE,IAAK92B,KAAK6E,QAAS,OAGnB,MAAMozB,EAAiBj4B,KAAK6E,QAAQsW,cAAc,gCAC9C8c,IACFA,EAAe3Y,YAActf,KAAKq2B,aAAe,SAInD,MAAM6B,EAAel4B,KAAK6E,QAAQsW,cAAc,gCAChD,GAAI+c,GAAgBl4B,KAAK2kB,WAAY,CACnC,MAAMyM,EAAQpxB,KAAK2kB,WAAW9Y,MAAMulB,OAASpxB,KAAK2kB,WAAW7R,SAC7DolB,EAAa5Y,YAAc8R,CAC7B,CACF,CAKA,aAAM+G,SACE54B,MAAM44B,UAERn4B,KAAKL,QAAQy4B,gBAAkBp4B,KAAKi3B,MAAMj3B,KAAK+1B,aAAe/1B,KAAKkR,SAASkd,cAC9EpuB,KAAKi3B,MAAMj3B,KAAK+1B,YAAc/1B,KAAKkR,SAASkd,YAAYnuB,IAG1DD,KAAKy2B,yBAGDz2B,KAAKiZ,WAAajZ,KAAKiZ,UAAUpU,SACnCiE,WAAW,KACT9I,KAAKiZ,UAAU6O,oBACf9nB,KAAKiZ,UAAU8W,mBACd,IAEP,CAKA,aAAM9D,SACEjsB,KAAKiZ,UAAUgT,SACvB,CAKA,gBAAA6I,GACE,OAAO90B,KAAKiZ,UAAU6b,kBACxB,CAKA,cAAAhE,GACE9wB,KAAKiZ,UAAU6X,gBACjB,CAKA,sBAAMkG,CAAiB1D,GAErB,MAAMF,EAAepzB,KAAKiZ,UAAUuO,yBAAyBxL,KAAKuX,GAAKA,EAAElb,MAAQib,GAC3E1W,EAAe5c,KAAK2kB,WAAWjkB,OAAO4yB,GAE5C,IAAKF,EAAc,OAGnB,MAAMrS,EAAQ,CACZ/e,KAAM,eACNC,MAAOmxB,EAAanxB,OAASqxB,EAC7BxpB,MAAO8S,KACJwW,EAAavL,QAGZnhB,QAAe4kB,EAAAA,OAAO4C,SAAS,CACnCrsB,MAAO,QAAQkf,EAAM9e,eACrBpC,KAAM,KACNiC,OAAQ,CAACif,KAGPra,QAAkC,IAAxBA,EAAO+tB,eACnBz0B,KAAKiZ,UAAUgW,UAAUqE,EAAW5sB,EAAO+tB,cAEvCz0B,KAAK2kB,WAAWwK,mBACZnvB,KAAK2kB,WAAWvV,cAElBpP,KAAKiZ,UAAU3U,SACrBtE,KAAK+2B,UAET,CAKA,eAAAsB,GACE,IAAKr4B,KAAK2kB,WAAY,OAGtB,MAAMuK,MAAEA,EAAArvB,KAAOA,EAAAuU,KAAMA,GAASpU,KAAK2kB,WAAWjkB,OAC9CV,KAAK2kB,WAAWjkB,OAAS,CAAEwuB,QAAOrvB,QAC9BuU,IAAMpU,KAAK2kB,WAAWjkB,OAAO0T,KAAOA,GAExCpU,KAAK+2B,UAED/2B,KAAK2kB,WAAWwK,YAClBnvB,KAAK2kB,WAAWvV,QAEhBpP,KAAKiZ,UAAU3U,QAEnB,CAEA,mBAAMg0B,CAAc5yB,GACXA,GAAU1F,KAAK2kB,YAAe3kB,KAAKL,QAAQy4B,gBAChDp4B,KAAKi3B,MAAMj3B,KAAK+1B,YAAcrwB,EAAMzF,GACpCD,KAAKy2B,yBACDz2B,KAAK2kB,YAAc3kB,KAAK2kB,WAAWwK,mBAC/BnvB,KAAK2kB,WAAWvV,QAE5B,CAKA,qBAAMmpB,GAEAv4B,KAAK2kB,aACP3kB,KAAK2kB,WAAW6T,IAAI,eACpBx4B,KAAK2kB,WAAW6T,IAAI,cAGlBx4B,KAAKiZ,YACPjZ,KAAKiZ,UAAUuf,IAAI,kBACnBx4B,KAAKiZ,UAAUuf,IAAI,gBACnBx4B,KAAKiZ,UAAUuf,IAAI,cACnBx4B,KAAKiZ,UAAUuf,IAAI,cACnBx4B,KAAKiZ,UAAUuf,IAAI,eACnBx4B,KAAKiZ,UAAUuf,IAAI,YACnBx4B,KAAKiZ,UAAUuf,IAAI,YACnBx4B,KAAKiZ,UAAUuf,IAAI,cACnBx4B,KAAKiZ,UAAUuf,IAAI,aACnBx4B,KAAKiZ,UAAUuf,IAAI,uBAGfj5B,MAAMg5B,iBACd,CAKA,cAAIE,GACF,OAAmC,IAA5Bz4B,KAAKL,QAAQ84B,UACtB,CAKA,aAAO72B,CAAOjC,EAAU,IACtB,OAAO,IAAIK,KAAKL,EAClB,ECzfF,MAAM+4B,gBAAgBx1B,EAAAA,KACpB,WAAA7D,CAAYM,EAAU,IACpB,MAAMg5B,KACJA,EAAAC,UACAA,EAAAC,UACAA,EAAAC,aACAA,EAAAC,SACAA,EAAAC,iBACAA,EAAAC,WACAA,EAAAC,cACAA,KACGC,GACDx5B,EAEJJ,MAAM,CACJoZ,QAAS,MACTC,UAAW,cACRugB,IAILn5B,KAAK24B,KAAO,CAAA,EACZ34B,KAAKo5B,UAAYlhB,OAAO6J,KAAK/hB,KAAK24B,MAClC34B,KAAK44B,UAAYA,GAAa54B,KAAKo5B,UAAU,IAAM,KAGnDp5B,KAAK64B,UAAYA,GAAa,oBAC9B74B,KAAK84B,aAAeA,GAAgB,cAGpC94B,KAAKk5B,cAAgBA,GAAiB,SACtCl5B,KAAK+4B,SAAWA,GAAY,IAC5B/4B,KAAKg5B,kBAAwC,IAArBA,EACxBh5B,KAAKi5B,WAAaA,GAAc,GAChCj5B,KAAKq5B,YAAc,OAGnBr5B,KAAKs5B,iCAAoBC,IACzBv5B,KAAKw5B,mBAAqB,EAC1Bx5B,KAAKy5B,eAAiB,KACtBz5B,KAAK05B,iBAAmB,KACxB15B,KAAK25B,kBAAoB,KAKzB35B,KAAK45B,cAAe,EACpB55B,KAAK65B,aAAc,EAGnB,IAAA,MAAY53B,EAAO63B,KAAS5hB,OAAOC,QAAQwgB,GACvC34B,KAAK+5B,OAAO93B,EAAO63B,GAIvB95B,KAAKg6B,aAAeh6B,KAAKg6B,aAAapQ,KAAK5pB,KAC7C,CAKA,oBAAMi6B,GAIJ,MAAO,qDAHej6B,KAAKk6B,iCACRl6B,KAAKm6B,uCAQ1B,CAMA,kBAAAD,GACE,OAA8B,IAA1Bl6B,KAAKo5B,UAAUtmB,OACV,GAGgB,aAArB9S,KAAKq5B,YACAr5B,KAAKo6B,0BAELp6B,KAAKq6B,qBAEhB,CAMA,mBAAAA,GACE,MAAMC,EAAWt6B,KAAKo5B,UAAUhhB,IAAInW,IAClC,MAAM0L,EAAW1L,IAAUjC,KAAK44B,UAC1B2B,EAAQv6B,KAAKw6B,SAASv4B,GAE5B,MAAO,0FAEuB0L,EAAW,SAAW,8BAClC4sB,uFAEYv6B,KAAKke,WAAWjc,wGAGjBs4B,wCACA5sB,oBACrB3N,KAAKke,WAAWjc,mDAIvBuX,KAAK,IAER,MAAO,sBACQxZ,KAAK64B,4DACdyB,sBAGR,CAMA,uBAAAF,GACE,MAAMK,EAAcz6B,KAAK44B,WAAa54B,KAAKo5B,UAAU,GAC/CxS,EAAgB5mB,KAAKo5B,UAAUhhB,IAAInW,IACvC,MAAM0L,EAAW1L,IAAUjC,KAAK44B,UAChC,MAAO,0DAE4BjrB,EAAW,SAAW,oFAE3B3N,KAAKke,WAAWjc,sDAEtCjC,KAAKke,WAAWjc,mBAChB0L,EAAW,sCAAwC,mDAI1D6L,KAAK,IAER,IAAIkhB,EA0BJ,OAvBEA,EAFyB,WAAvB16B,KAAKk5B,cAEM,0NAKgBl5B,KAAKC,uDACQD,KAAKke,WAAWuc,uCAK7C,0OAKgBz6B,KAAKC,8DAE5BD,KAAKke,WAAWuc,gCAKjB,yEAEDC,sEACwD16B,KAAKC,mBAC3D2mB,sCAIV,CAMA,6BAAA+T,GACE,MAAMF,EAAcz6B,KAAK44B,WAAa54B,KAAKo5B,UAAU,GAC/CxS,EAAgB5mB,KAAKo5B,UAAUhhB,IAAInW,IACvC,MAAM0L,EAAW1L,IAAUjC,KAAK44B,UAChC,MAAO,0DAE4BjrB,EAAW,SAAW,oFAE3B3N,KAAKke,WAAWjc,sDAEtCjC,KAAKke,WAAWjc,mBAChB0L,EAAW,mCAAqC,mDAIvD6L,KAAK,IAER,MAAO,gUAOCxZ,KAAKke,WAAWuc,8EAGhB7T,sCAIV,CAMA,uBAAAgU,GACE,IAAK56B,KAAK66B,qBAAsB,OAAO,EAGvC,MAAMC,EAAgBzmB,OAAO0mB,WAC7B,OAAID,EAAgB96B,KAAKg7B,kBAKlBh7B,KAAK65B,aAAeiB,EAAgB,GAC7C,CAMA,eAAAX,GACE,GAA8B,IAA1Bn6B,KAAKo5B,UAAUtmB,OACjB,MAAO,yDAGT,MAAMmoB,EAAWj7B,KAAKo5B,UAAUhhB,IAAInW,IAClC,MAAM0L,EAAW1L,IAAUjC,KAAK44B,UAC1B2B,EAAQv6B,KAAKw6B,SAASv4B,GAE5B,MAAO,uCACuB0L,EAAW,cAAgB,yBAC5C4sB,mEAEaA,wCACDv6B,KAAKke,WAAWjc,wCACds4B,8CAG1B/gB,KAAK,IAER,MAAO,uBACSxZ,KAAK84B,2BACfmC,uBAGR,CAOA,QAAAT,CAASv4B,GACP,MAAO,OAAOA,EAAMi5B,cAAcxQ,QAAQ,aAAc,QAAQ1qB,KAAKC,IACvE,CAOA,aAAMk7B,CAAQC,EAAUz7B,EAAU,IAChC,MAAM03B,MAAEA,GAAQ,GAAU13B,EAG1B,IAAKK,KAAK24B,KAAKyC,GAEb,OADAp2B,QAAQgC,KAAK,iBAAiBo0B,sBACvB,EAMT,GAAIp7B,KAAK44B,YAAcwC,IAAa/D,EAAO,CAGzC,MAAMgE,EAAar7B,KAAK24B,KAAKyC,GAC7B,GAAIC,GAAcA,EAAWC,aAAet7B,KAAK6E,QAAQub,SAASib,EAAWx2B,SAC3E,OAAO,CAEX,CAEA,MAAM02B,EAAcv7B,KAAK44B,UACzB54B,KAAK44B,UAAYwC,EAEjB,IAaE,aAXMp7B,KAAKw7B,oBAAoBJ,EAAUG,SAGnCv7B,KAAKy7B,iBAAiBL,EAAUG,GAGtCv7B,KAAK+E,KAAK,cAAe,CACvB6zB,UAAWwC,EACXG,iBAGK,CACT,OAASl7B,GAGP,OAFA2E,QAAQ3E,MAAM,8BAA+BA,GAC7CL,KAAK44B,UAAY2C,GACV,CACT,CACF,CAOA,yBAAMC,CAAoBE,EAAgBC,GACxC,IAAK37B,KAAK6E,QAAS,OAGnB,GAAyB,aAArB7E,KAAKq5B,YAEP,kBADMr5B,KAAK47B,sBAKb,GAAID,EAAkB,CACpB,MAAME,EAAgB77B,KAAK6E,QAAQsW,cAAc,oBAAoBwgB,OACjEE,IACFA,EAAcngB,UAAUwD,OAAO,UAC/B2c,EAAclgB,aAAa,gBAAiB,SAEhD,CAGA,MAAMmgB,EAAkB97B,KAAK6E,QAAQsW,cAAc,oBAAoBugB,OACnEI,IACFA,EAAgBpgB,UAAUzH,IAAI,UAC9B6nB,EAAgBngB,aAAa,gBAAiB,QAElD,CAOA,sBAAM8f,CAAiBC,EAAgBC,GACrC,IAAK37B,KAAK6E,QAAS,OAEnB,MAAMk3B,EAAc/7B,KAAKw6B,SAASkB,GAC5BM,EAAgBL,EAAmB37B,KAAKw6B,SAASmB,GAAoB,KAG3E,GAAIK,EAAe,CACjB,MAAMC,EAAWj8B,KAAK6E,QAAQsW,cAAc,IAAI6gB,KAC5CC,GACFA,EAASvgB,UAAUwD,OAAO,OAAQ,SAEtC,CAGA,MAAMgd,EAAal8B,KAAK6E,QAAQsW,cAAc,IAAI4gB,KAC9CG,GACFA,EAAWxgB,UAAUzH,IAAI,OAAQ,UAInC,MAAMonB,EAAar7B,KAAK24B,KAAK+C,GAC7B,GAAIL,EAAY,CACd,MAAMtT,EAAY/nB,KAAK6E,QAAQsW,cAAc,oBAAoB4gB,eAC7DhU,IAAcsT,EAAWC,mBACrBD,EAAW/2B,QAAO,EAAMyjB,GAE5BsT,EAAWc,sBACPd,EAAWc,gBAErB,CACF,CAOA,qBAAMC,CAAgBx3B,EAAOC,GAC3B,MAAMu2B,EAAWv2B,EAAQkX,aAAa,kBAClCqf,SACIp7B,KAAKm7B,QAAQC,EAEvB,CAKA,4BAAAiB,GACE,IAAKr8B,KAAK6E,SAAW7E,KAAK25B,kBAAmB,OAE7C,MAAM2C,EAAYt8B,KAAK6E,QAAQsW,cAAc,aAC7C,GAAImhB,GAAgD,mBAA5BjoB,OAAOkoB,iBAAiC,CAC9D,MAAMvf,EAAQ3I,OAAOkoB,iBAAiBD,GACtCt8B,KAAK25B,kBAAoB,CACvB6C,KAAMxf,EAAMwf,KACZC,cAAezf,EAAMyf,eAIvB,MAAMC,EAAclX,WAAWxI,EAAM0f,cAAgB,EAC/CC,EAAenX,WAAWxI,EAAM2f,eAAiB,EACvD38B,KAAKi5B,WAAayD,EAAcC,EAAe,EACjD,CACF,CAKA,mBAAM1hB,SACE1b,MAAM0b,gBAGZjb,KAAKq8B,+BAGDr8B,KAAKg5B,kBACPh5B,KAAK48B,0BAKH58B,KAAK44B,WAAa54B,KAAK24B,KAAK34B,KAAK44B,kBAC7B54B,KAAKm7B,QAAQn7B,KAAK44B,UAAW,CAAEvB,OAAO,GAEhD,CAKA,kBAAMwF,SACEt9B,MAAMs9B,cAKd,CAKA,qBAAMtE,SACEh5B,MAAMg5B,kBAGRv4B,KAAKy5B,iBACPz5B,KAAKy5B,eAAeqD,aACpB98B,KAAKy5B,eAAiB,MAIF,oBAAXplB,QACTA,OAAOyZ,oBAAoB,SAAU9tB,KAAKg6B,cAIxCh6B,KAAK05B,kBAAoB15B,KAAK05B,iBAAiBqD,eACjD/8B,KAAK05B,iBAAiBqD,cAAcC,YAAYh9B,KAAK05B,kBAEvD15B,KAAK05B,iBAAmB,KAGxB,IAAA,MAAYz3B,EAAO63B,KAAS5hB,OAAOC,QAAQnY,KAAK24B,MAC1CmB,GAAgC,mBAAjBA,EAAKxN,eAChBwN,EAAKxN,SAGjB,CAMA,YAAA2Q,GACE,OAAOj9B,KAAK44B,SACd,CAMA,YAAAsE,GACE,MAAO,IAAIl9B,KAAKo5B,UAClB,CAOA,MAAA+D,CAAOl7B,GACL,OAAOjC,KAAK24B,KAAK12B,IAAU,IAC7B,CASA,YAAM83B,CAAO93B,EAAO63B,EAAMsD,GAAa,GACrC,OAAIp9B,KAAK24B,KAAK12B,IACZ+C,QAAQgC,KAAK,iBAAiB/E,sBACvB,KAGL63B,EAAKn6B,QAAQ0V,cAAgBrV,KAAKkR,SAASqd,WAAW8O,QAAQvD,EAAKn6B,QAAQ0V,eAI/ErV,KAAK24B,KAAK12B,GAAS63B,EAEnBA,EAAKxQ,YAActpB,KAAKw6B,SAASv4B,GACjC63B,EAAKwD,OAASt9B,KACdA,KAAKo5B,UAAYlhB,OAAO6J,KAAK/hB,KAAK24B,MAG7B34B,KAAK44B,YAAawE,IACrBp9B,KAAK44B,UAAY32B,GAIfjC,KAAKs7B,oBACDt7B,KAAKsE,UAEP84B,GAAcp9B,KAAK44B,YAAc32B,UAC7BjC,KAAKm7B,QAAQl5B,IAIvBjC,KAAK+E,KAAK,YAAa,CAAE9C,QAAO63B,SACzB,GACT,CAOA,eAAMyD,CAAUt7B,GACd,IAAKjC,KAAK24B,KAAK12B,GAEb,OADA+C,QAAQgC,KAAK,iBAAiB/E,sBACvB,EAGT,MAAM63B,EAAO95B,KAAK24B,KAAK12B,GA0BvB,OAvBI63B,GAAgC,mBAAjBA,EAAKxN,eAChBwN,EAAKxN,iBAINtsB,KAAK24B,KAAK12B,GACjBjC,KAAKo5B,UAAYlhB,OAAO6J,KAAK/hB,KAAK24B,MAG9B34B,KAAK44B,YAAc32B,IACrBjC,KAAK44B,UAAY54B,KAAKo5B,UAAU,IAAM,MAIpCp5B,KAAKs7B,oBACDt7B,KAAKsE,SAEPtE,KAAK44B,iBACD54B,KAAKm7B,QAAQn7B,KAAK44B,YAI5B54B,KAAK+E,KAAK,cAAe,CAAE9C,QAAO63B,UAC3B,CACT,CAOA,iBAAA0D,CAAkBv7B,GAChB,GAAIjC,KAAKs5B,cAAcpd,IAAIja,GACzB,OAAOjC,KAAKs5B,cAAchvB,IAAIrI,GAIhC,GAAwB,oBAAbkb,SAA0B,CACnC,MAAMsgB,EAAgC,EAAfx7B,EAAM6Q,OAAa9S,KAAKi5B,WAE/C,OADAj5B,KAAKs5B,cAAc9xB,IAAIvF,EAAOw7B,GACvBA,CACT,CAGKz9B,KAAK05B,mBACR15B,KAAK05B,iBAAmBvc,SAASC,cAAc,QAC/Cpd,KAAK05B,iBAAiB1c,MAAM1D,WAAa,SACzCtZ,KAAK05B,iBAAiB1c,MAAM0gB,SAAW,WACvC19B,KAAK05B,iBAAiB1c,MAAM2gB,WAAa,UAG3C,MAAMC,EAAO59B,KAAK05B,iBAGd15B,KAAK25B,mBACPiE,EAAK5gB,MAAMwf,KAAOx8B,KAAK25B,kBAAkB6C,KACzCoB,EAAK5gB,MAAMyf,cAAgBz8B,KAAK25B,kBAAkB8C,gBAGlDmB,EAAK5gB,MAAM+I,SAAW,OACtB6X,EAAK5gB,MAAM6gB,WAAa,wCAG1BD,EAAKte,YAAcrd,EACnBkb,SAASsO,KAAKpO,YAAYugB,GAC1B,MAAME,EAAQF,EAAKG,YAAc/9B,KAAKi5B,WAItC,OAHA9b,SAASsO,KAAKuR,YAAYY,GAE1B59B,KAAKs5B,cAAc9xB,IAAIvF,EAAO67B,GACvBA,CACT,CAMA,gBAAAE,GACE,OAAOh+B,KAAKo5B,UAAU5lB,OAAO,CAAC5P,EAAO3B,IAC5B2B,EAAQ5D,KAAKw9B,kBAAkBv7B,GACrC,EACL,CAMA,iBAAAg8B,GACE,OAAKj+B,KAAK6E,UAIQ7E,KAAK6E,QAAQk4B,eAAiB/8B,KAAK6E,SACpCk5B,aAJR/9B,KAAK+4B,QAKhB,CAMA,iBAAAmF,GACE,IAAKl+B,KAAKg5B,iBACR,OAAO,EAGT,MAAMmF,EAAiBn+B,KAAKi+B,oBACtBG,EAAgBp+B,KAAKg+B,mBAG3B,OAAOG,EAAiBl2B,KAAKwqB,IAAI2L,EAAep+B,KAAK+4B,SACvD,CAKA,uBAAA6D,GACE,GAAK58B,KAAK6E,SAAY7E,KAAKg5B,iBAQ3B,GAHAh5B,KAAKq+B,uBAGyB,oBAAnBC,eAAgC,CACzCt+B,KAAKy5B,eAAiB,IAAI6E,eAAe,KACvCt+B,KAAKg6B,iBAGP,MAAMjS,EAAY/nB,KAAK6E,QAAQk4B,eAAiB/8B,KAAK6E,QACrD7E,KAAKy5B,eAAe8E,QAAQxW,EAC9B,MAEE1T,OAAOoK,iBAAiB,SAAUze,KAAKg6B,aAE3C,CAKA,kBAAMA,GACJ,MAAMmE,EAAiBn+B,KAAKi+B,oBAGxBh2B,KAAKu2B,IAAIL,EAAiBn+B,KAAKw5B,oBAAsB,KACvDx5B,KAAKw5B,mBAAqB2E,QACpBn+B,KAAKq+B,uBAEf,CAKA,0BAAMA,GACJ,MACMI,EADoBz+B,KAAKk+B,oBACK,WAAa,OAE7CO,IAAYz+B,KAAKq5B,cACnBr5B,KAAKq5B,YAAcoF,EAGfz+B,KAAKs7B,mBACDt7B,KAAK47B,qBAIb57B,KAAK+E,KAAK,yBAA0B,CAClC25B,KAAM1+B,KAAKq5B,YACX8E,eAAgBn+B,KAAKi+B,oBACrBG,cAAep+B,KAAKg+B,qBAG1B,CAKA,wBAAMpC,GACJ,IAAK57B,KAAK6E,QAAS,OAEnB,MAAM85B,EAAsB3+B,KAAK6E,QAAQsW,cAAc,mBACvD,GAAIwjB,EAAqB,CACvB,MAAMC,EAAgB5+B,KAAKk6B,qBAC3ByE,EAAoBE,UAAYD,CAClC,CACF,CAMA,iBAAAE,GACE,OAAO9+B,KAAKq5B,WACd,CAMA,uBAAM0F,CAAkBL,GACT,SAATA,GAA4B,aAATA,GAKvB1+B,KAAKq5B,YAAcqF,EAEf1+B,KAAKs7B,mBACDt7B,KAAK47B,sBAPX52B,QAAQgC,KAAK,6DASjB,CAKA,eAAAg4B,GACEh/B,KAAKs5B,cAAc2F,OACrB,CAOA,aAAOr9B,CAAOjC,EAAU,IACtB,OAAO,IAAI+4B,QAAQ/4B,EACrB,EChzBF,MAAMu/B,wBAAwBh8B,EAAAA,KAC1B,WAAA7D,CAAYM,EAAU,IAClBJ,MAAM,CACFqZ,UAAW,kBACRjZ,IAEPK,KAAKyF,KAAO9F,EAAQ8F,MAAQ,CAAA,EAC5BzF,KAAKqK,QAAUrK,KAAKyF,KAAK4B,cAAc+nB,WAAW,UAClDpvB,KAAKm/B,MAAmC,oBAA3Bn/B,KAAKyF,KAAK4B,YAC3B,CAEA,WAAAlD,GACI,MAAO,4MAIWnE,KAAKqK,QAAU,aAAarK,KAAKyF,KAAK25B,cAAgBp/B,KAAKyF,KAAKnF,8EAAgF,ykBAYtK,CAEA,sBAAM++B,GACF,GAAIr/B,KAAKqK,QAAS,CAEd,MAAMi1B,EAAkBjrB,OAAOkrB,MAAMC,SAASF,gBAE1CA,EACAA,EAAgB3lB,KAAK,CAAE8lB,IAAKz/B,KAAKyF,KAAKnF,IAAKo/B,IAAK1/B,KAAKyF,KAAKrC,WAG1DiR,OAAO5L,KAAKzI,KAAKyF,KAAKnF,IAAK,SAEnC,MAAA,GAAWN,KAAKm/B,MAAO,CAEnB,MAAMQ,EAAYtrB,OAAOkrB,MAAMC,SAASG,UAEpCA,EACAA,EAAUpU,WAAWvrB,KAAKyF,KAAKnF,IAAK,CAAEuB,MAAO7B,KAAKyF,KAAKrC,WAGvDiR,OAAO5L,KAAKzI,KAAKyF,KAAKnF,IAAK,SAEnC,MACI+T,OAAO5L,KAAKzI,KAAKyF,KAAKnF,IAAK,SAEnC,EC9CJ,MAAMs/B,wBAAwB18B,EAAAA,KAC1B,WAAA7D,CAAYM,EAAU,IAClBJ,MAAM,CACFqZ,UAAW,kBACRjZ,IAGPK,KAAKY,QAAUjB,EAAQiB,SAAW,CAAA,EAClCZ,KAAK6/B,MAAQlgC,EAAQkgC,OAAS,UAC9B7/B,KAAK8/B,cAAgBngC,EAAQmgC,gBAAiB,EAG3B,YAAf9/B,KAAK6/B,QACL7/B,KAAK4Y,WAAa5Y,KAAK8/B,cAAgB,iBAAmB,gBAElE,CAEA,WAAA37B,GAEI,MAA0B,iBAAtBnE,KAAKY,QAAQmB,KACN,qQASQ,YAAf/B,KAAK6/B,MACE7/B,KAAK+/B,qBAEL//B,KAAKggC,oBAEpB,CAKA,kBAAAA,GAGI,MAAO,wFAFWhgC,KAAK8/B,cAAgB,aAAe,spCA2B1D,CAKA,kBAAAC,GACI,MAAO,yfAYX,CAEA,mBAAM9kB,GAEF,GAAIjb,KAAKY,QAAQq/B,aAAejgC,KAAKY,QAAQq/B,YAAYntB,OAAS,EAAG,CACjE,MAAMotB,EAAuBlgC,KAAK6E,QAAQsW,cAAc,kCACpD+kB,GACAlgC,KAAKY,QAAQq/B,YAAYjsB,QAAQvO,IAC7B,MAAM06B,EAAc,IAAIjB,gBAAgB,CAAEz5B,SAC1CzF,KAAK22B,SAASwJ,GACdA,EAAY77B,QAAO,EAAM47B,IAGrC,CACJ,ECtGJ,MAAME,sBAAsBl9B,EAAAA,KACxB,WAAA7D,CAAYM,EAAU,IAClBJ,MAAM,CACFqZ,UAAW,qBACRjZ,IAGPK,KAAKqC,YAAc1C,EAAQ0C,aAAe,oBAC1CrC,KAAKqgC,WAAa1gC,EAAQ0gC,YAAc,OACxCrgC,KAAKigC,YAAc,GACnBjgC,KAAKsgC,kCAAqB/G,GAC9B,CAEA,WAAAp1B,GACI,MAAO,6TAMwBnE,KAAKqC,orBAexC,CAEA,mBAAM4Y,GAEFjb,KAAKugC,eAAe,CAChBC,iBAAkB,wBAClBC,UAAU,EACVC,cAAe,CAAC,OAChBC,gBAAgB,EAChBC,cAAe,YACfC,gBAAiB,gBAIrB,MAAMC,EAAW9gC,KAAK6E,QAAQsW,cAAc,eACxC2lB,IACAA,EAASriB,iBAAiB,QAAS,IAAMze,KAAK+gC,mBAAmBD,IACjEA,EAASriB,iBAAiB,UAAYC,GAAM1e,KAAKghC,cAActiB,IAEvE,CAKA,aAAAsiB,CAAcp8B,GACQ,UAAdA,EAAMyT,KAAoBzT,EAAMq8B,WAChCr8B,EAAM+Z,iBACN3e,KAAKkhC,oBAAoBt8B,EAAOA,EAAMyX,QAE9C,CAMA,gBAAM8kB,CAAWC,GACb,IAAA,MAAW37B,KAAQ27B,QACTphC,KAAKqhC,WAAW57B,EAE9B,CAMA,gBAAM47B,CAAW57B,GACb,MAAMD,EAAY,IAAIQ,EAChBs7B,EAAWxyB,KAAK4C,MAAQzJ,KAAKs5B,SAGnCvhC,KAAKwhC,eAAeF,EAAU77B,EAAM,GACpCzF,KAAKsgC,eAAe94B,IAAI85B,EAAU,CAAE77B,OAAMD,cAE1C,UACyBA,EAAUuC,OAAO,CAClCtC,OACAG,WAAanC,IACTzD,KAAKyhC,mBAAmBH,EAAU79B,IAEtCoC,WAAa67B,IACT1hC,KAAK2hC,qBAAqBL,EAAU97B,KAIhD,OAASnF,GACL2E,QAAQ3E,MAAM,sBAAuBA,GACrCL,KAAK4hC,kBAAkBN,EAAUjhC,EACrC,CACJ,CAQA,cAAAmhC,CAAeF,EAAU77B,EAAMhC,GAC3B,MAAMskB,EAAY/nB,KAAK6E,QAAQsW,cAAc,kCAC7C,IAAK4M,EAAW,OAEhB,MAAM8Z,EAAU1kB,SAASC,cAAc,OACvCykB,EAAQjpB,UAAY,qBACpBipB,EAAQpkB,QAAQ6jB,SAAWA,EAC3BO,EAAQrmB,UAAY,kJAGoBxb,KAAKke,WAAWzY,EAAKzD,gEACpBhC,KAAK8hC,eAAer8B,EAAK5F,iOAIO4D,yLAG+C69B,8FAKxHvZ,EAAU1K,YAAYwkB,EAC1B,CAOA,kBAAAJ,CAAmBH,EAAU79B,GACzB,MAAMo+B,EAAU7hC,KAAK6E,QAAQsW,cAAc,oBAAoBmmB,OAC/D,GAAIO,EAAS,CACT,MAAME,EAAcF,EAAQ1mB,cAAc,iBACtC4mB,IACAA,EAAY/kB,MAAM8gB,MAAQ,GAAGr6B,KAErC,CACJ,CAOA,oBAAAk+B,CAAqBL,EAAU97B,GAE3BxF,KAAKigC,YAAYvmB,KAAK,CAClBzZ,GAAIuF,EAAUvF,GACd+B,KAAMwD,EAAU8E,IAAI,QACpBg3B,aAEJthC,KAAKsgC,eAAelhB,OAAOkiB,GAE3B,MAAMO,EAAU7hC,KAAK6E,QAAQsW,cAAc,oBAAoBmmB,OAC/D,GAAIO,EAAS,CACTA,EAAQnmB,UAAUzH,IAAI,mBACtB,MAAM+tB,EAAoBH,EAAQ1mB,cAAc,wBAC5C6mB,GACAA,EAAkB9iB,QAE1B,CACJ,CAOA,iBAAA0iB,CAAkBN,EAAUjhC,GACxBL,KAAKsgC,eAAelhB,OAAOkiB,GAE3B,MAAMO,EAAU7hC,KAAK6E,QAAQsW,cAAc,oBAAoBmmB,OAC3DO,IACAA,EAAQnmB,UAAUzH,IAAI,gBACtB4tB,EAAQ1mB,cAAc,oBAAoBK,WACtC,sDAEZ,CAKA,8BAAMymB,CAAyBr9B,EAAOC,GAClC,MAAMy8B,EAAWz8B,EAAQ4Y,QAAQ6jB,SAGjCthC,KAAKsgC,eAAelhB,OAAOkiB,GAG3B,MAAMO,EAAU7hC,KAAK6E,QAAQsW,cAAc,oBAAoBmmB,OAC3DO,GAEAA,EAAQ3iB,QAEhB,CAOA,yBAAMgiB,CAAoBt8B,EAAOC,GAC7B,MACMkF,EADW/J,KAAK6E,QAAQsW,cAAc,eACtBrR,MAAM0X,QAGvBzX,GAAoC,IAA5B/J,KAAKigC,YAAYntB,UAK1B9S,KAAKsgC,eAAezgC,KAAO,IAM/BG,KAAKkiC,SAAQ,GAGbliC,KAAK+E,KAAK,eAAgB,CACtBgF,OACAq3B,MAAOphC,KAAKigC,eAIpB,CAMA,OAAAiC,CAAQC,GACJ,MAAMrb,EAAS9mB,KAAK6E,QAAQsW,cAAc,kBACpCT,EAAOoM,EAAO3L,cAAc,iBAC5BinB,EAAUtb,EAAO3L,cAAc,mBAEjCgnB,GACArb,EAAOhiB,UAAW,EAClB4V,EAAKgB,UAAUzH,IAAI,UACnBmuB,EAAQ1mB,UAAUwD,OAAO,YAEzB4H,EAAOhiB,UAAW,EAClB4V,EAAKgB,UAAUwD,OAAO,UACtBkjB,EAAQ1mB,UAAUzH,IAAI,UAE9B,CAKA,UAAAouB,GACI,MAAMvB,EAAW9gC,KAAK6E,QAAQsW,cAAc,eACxC2lB,IACAA,EAASh3B,MAAQ,GACjBg3B,EAAS9jB,MAAMslB,OAAS,QAG5B,MAAMva,EAAY/nB,KAAK6E,QAAQsW,cAAc,kCACzC4M,IACAA,EAAUvM,UAAY,IAG1Bxb,KAAKigC,YAAc,GACnBjgC,KAAKsgC,eAAerB,QAGpBj/B,KAAKkiC,SAAQ,EACjB,CAMA,kBAAAnB,CAAmBD,GACfA,EAAS9jB,MAAMslB,OAAS,OACxBxB,EAAS9jB,MAAMslB,OAASr6B,KAAK8J,IAAI+uB,EAASyB,aAAc,KAAO,IACnE,CAOA,cAAAT,CAAeU,GACX,GAAc,IAAVA,EAAa,MAAO,MACxB,MAEMrQ,EAAIlqB,KAAKoK,MAAMpK,KAAKw6B,IAAID,GAASv6B,KAAKw6B,IAFlC,OAGV,OAAOjd,YAAYgd,EAAQv6B,KAAKy6B,IAHtB,KAG6BvQ,IAAIzjB,QAAQ,IAAM,IAF3C,CAAC,IAAK,KAAM,KAAM,MAEqCyjB,EACzE,CAOA,UAAAjU,CAAWnU,GACP,MAAMsV,EAAMlC,SAASC,cAAc,OAEnC,OADAiC,EAAIC,YAAcvV,EACXsV,EAAI7D,SACf,EAIJmnB,EAAAA,mBAAmBvC,eCjTnB,MAAMwC,iBAAiB1/B,EAAAA,KACnB,WAAA7D,CAAYM,EAAU,IAClBJ,MAAM,CACFqZ,UAAW,eACRjZ,IAGPK,KAAK6iC,QAAUljC,EAAQkjC,QACvB7iC,KAAK6/B,MAAQlgC,EAAQkgC,OAAS,UAC9B7/B,KAAK8iC,cAAgBnjC,EAAQmjC,cAC7B9iC,KAAK+iC,iBAAmBpjC,EAAQojC,kBAAoB,oBACpD/iC,KAAKgjC,gBAAkBrjC,EAAQqjC,iBAAmB,OAClDhjC,KAAKijC,SAAW,GAChBjjC,KAAKkjC,gCAAmB3J,GAC5B,CAEA,WAAAp1B,GACI,MAAO,uDACqCnE,KAAK6/B,mMAKrD,CAEA,YAAMrJ,GAEFx2B,KAAKijC,eAAiBjjC,KAAK6iC,QAAQzzB,QAGnCpP,KAAKmjC,UAAY,IAAI/C,cAAc,CAC/B9W,YAAa,QACbjnB,YAAarC,KAAK+iC,iBAClB1C,WAAYrgC,KAAKgjC,kBAErBhjC,KAAK22B,SAAS32B,KAAKmjC,WAGnBnjC,KAAKmjC,UAAUve,GAAG,eAAgBnjB,MAAOnC,UAC/BU,KAAKojC,kBAAkB9jC,IAErC,CAEA,mBAAM2b,GAEFjb,KAAKqjC,2BAGCrjC,KAAKsjC,kBAGXtjC,KAAKujC,gBACT,CAMA,qBAAMD,SACI/jC,MAAM+jC,kBACZ,MAAME,EAAoBxjC,KAAK6E,QAAQsW,cAAc,+BAChDqoB,EAMLxjC,KAAKkjC,aAAalvB,QAASyvB,IACvBD,EAAkBnmB,YAAYomB,EAAY5+B,SAC1C4+B,EAAYn/B,QAAO,KAPnBU,QAAQ3E,MAAM,yCAStB,CAMA,kBAAAgjC,GACSrjC,KAAKijC,UAAqC,IAAzBjjC,KAAKijC,SAASnwB,QAEpC9S,KAAKijC,SAASjvB,QAAQpT,IACbZ,KAAKkjC,aAAahnB,IAAItb,EAAQX,KAC/BD,KAAK0jC,mBAAmB9iC,IAGpC,CAMA,kBAAA8iC,CAAmB9iC,GACf,GAAIZ,KAAKkjC,aAAahnB,IAAItb,EAAQX,IAAK,OAEvC,MAAM6/B,EAAgBl/B,EAAQ+iC,QAAU/iC,EAAQ+iC,OAAO1jC,KAAOD,KAAK8iC,cAE7DW,EAAc,IAAI7D,gBAAgB,CACpCh/B,UACAi/B,MAAO7/B,KAAK6/B,MACZC,kBAMJ,OAHA9/B,KAAK22B,SAAS8M,GACdzjC,KAAKkjC,aAAa17B,IAAI5G,EAAQX,GAAIwjC,GAE3BA,CACX,CAOA,UAAAG,CAAWhjC,EAASijC,GAAS,GACzB,GAAI7jC,KAAKkjC,aAAahnB,IAAItb,EAAQX,IAAK,OAEvC,MAAMwjC,EAAczjC,KAAK0jC,mBAAmB9iC,GAG5C,GAAIZ,KAAKs7B,YAAa,CAClB,MAAMkI,EAAoBxjC,KAAK6E,QAAQsW,cAAc,+BACjDqoB,IACAA,EAAkBnmB,YAAYomB,EAAY5+B,SAC1C4+B,EAAYn/B,QAAO,GAE3B,CAEIu/B,GACA7jC,KAAKujC,gBAEb,CAOA,uBAAMH,CAAkB9jC,GACpB,IAEI,GAAIA,EAAKyK,MAAQzK,EAAKyK,KAAKyX,gBACFxhB,KAAK6iC,QAAQiB,QAAQ,CACtC/5B,KAAMzK,EAAKyK,KACXq3B,MAAO9hC,EAAK8hC,OAAS9hC,EAAK8hC,MAAMtuB,OAAS,EAAI,CAACxT,EAAK8hC,MAAM,IAAM,MAGvDjhC,QACR,MAAM,IAAI8F,MAAM,0BAOxB,IAAA,IAASksB,EAFW7yB,EAAKyK,MAAQzK,EAAKyK,KAAKyX,QAAUliB,EAAK8hC,MAAMtuB,OAAS,EAAK,EAAI,EAEzDqf,GAAK7yB,EAAK8hC,OAAOtuB,QAAU,GAAIqf,IAAK,CACzD,MAAM1sB,EAAOnG,EAAK8hC,MAAMjP,UACHnyB,KAAK6iC,QAAQiB,QAAQ,CACtC/5B,KAAM,GACNq3B,MAAO,CAAC37B,MAGAtF,SACR6E,QAAQ3E,MAAM,yBAA0BoF,EAEhD,CAGAzF,KAAKijC,eAAiBjjC,KAAK6iC,QAAQzzB,QAGnCpP,KAAKijC,SAASjvB,QAAQpT,IACbZ,KAAKkjC,aAAahnB,IAAItb,EAAQX,KAC/BD,KAAK4jC,WAAWhjC,GAAS,KAKjCZ,KAAKmjC,UAAUd,YAEnB,OAAShiC,GACL2E,QAAQ3E,MAAM,0BAA2BA,GAEzCL,KAAKmjC,UAAUjB,SAAQ,EAE3B,CACJ,CAKA,cAAAqB,GACI,MAAMxb,EAAY/nB,KAAK6E,QAAQsW,cAAc,kBACzC4M,GACAgc,sBAAsB,KAClBhc,EAAUic,UAAYjc,EAAUwa,cAG5C,CAKA,aAAA0B,GACIjkC,KAAKkjC,aAAalvB,QAAQ8lB,GAAQA,EAAKxN,WACvCtsB,KAAKkjC,aAAajE,QAClBj/B,KAAKijC,SAAW,GAEhB,MAAMlb,EAAY/nB,KAAK6E,QAAQsW,cAAc,+BACzC4M,IACAA,EAAUvM,UAAY,GAE9B,CAKA,aAAMyQ,GACFjsB,KAAKikC,gBACLjkC,KAAKijC,eAAiBjjC,KAAK6iC,QAAQzzB,QACnCpP,KAAKqjC,qBAEDrjC,KAAKs7B,oBACCt7B,KAAKsjC,kBACXtjC,KAAKujC,iBAEb,2QrBvDqB,CACvB3hC,OAAQ,CACNC,MAAO,mBACPC,OAAQ,CACN,CACEE,KAAM,OACND,KAAM,OACNE,MAAO,cACPI,YAAa,cACbE,UAAU,EACVE,QAAS,GACTE,KAAM,2DAER,CACEX,KAAM,SACND,KAAM,OACNE,MAAO,wBACPI,YAAa,YACbI,QAAS,GACTE,KAAM,wDAER,CACEX,KAAM,UACND,KAAM,OACNE,MAAO,qBACPI,YAAa,0CACbI,QAAS,GACTE,KAAM,0CAER,CACEX,KAAM,aACND,KAAM,OACNE,MAAO,wBACPI,YAAa,6CACbI,QAAS,GACTE,KAAM,6CAER,CACEX,KAAM,oBACND,KAAM,SACNE,MAAO,2BACPQ,QAAS,GACTE,KAAM,qEAKZC,KAAM,CACJf,MAAO,oBACPC,OAAQ,CACN,CACEE,KAAM,OACND,KAAM,OACNE,MAAO,cACPI,YAAa,cACbE,UAAU,EACVE,QAAS,GACTkU,UAAU,EACVhU,KAAM,iDAER,CACEX,KAAM,SACND,KAAM,OACNE,MAAO,aACPI,YAAa,YACbI,QAAS,IAEX,CACET,KAAM,oBACND,KAAM,SACNE,MAAO,2BACPQ,QAAS,IAEX,CACET,KAAM,oBACND,KAAM,OACNE,MAAO,oBACPI,YAAa,oBACbI,QAAS,IAEX,CACET,KAAM,oBACND,KAAM,OACNE,MAAO,oBACPI,YAAa,uBACbI,QAAS,IAEX,CACET,KAAM,WACND,KAAM,SACNE,MAAO,WACPtC,QAAS,CACP,CAAEmK,MAAO,SAAUC,KAAM,yBACzB,CAAED,MAAO,UAAWC,KAAM,4BAE5BtH,QAAS,MAKf2H,YAAa,CACXtI,OAAQ,CACJ,CACIE,KAAM,SACND,KAAM,SACNE,MAAO,wBACPI,YAAa,YACb1C,QAAS,CACL,CAAEmK,MAAO,YAAaC,KAAM,yBAC5B,CAAED,MAAO,YAAaC,KAAM,kBAC5B,CAAED,MAAO,YAAaC,KAAM,2BAC5B,CAAED,MAAO,YAAaC,KAAM,oBAC5B,CAAED,MAAO,eAAgBC,KAAM,oBAC/B,CAAED,MAAO,YAAaC,KAAM,oBAC5B,CAAED,MAAO,YAAaC,KAAM,mBAC5B,CAAED,MAAO,YAAaC,KAAM,kBAC5B,CAAED,MAAO,eAAgBC,KAAM,sBAC/B,CAAED,MAAO,aAAcC,KAAM,sBAC7B,CAAED,MAAO,aAAcC,KAAM,kBAC7B,CAAED,MAAO,iBAAkBC,KAAM,0BAErCtH,QAAS,GACTE,KAAM,wDAEV,CACIX,KAAM,UACND,KAAM,OACNE,MAAO,qBACPI,YAAa,0CACbI,QAAS,GACTE,KAAM,0CAEV,CACIX,KAAM,aACND,KAAM,OACNE,MAAO,wBACPI,YAAa,6CACbI,QAAS,GACTE,KAAM,+CAKhB5C,QAAS,CACP8B,MAAO,iBACPC,OAAQ,CACN,CACEC,KAAM,SACNgI,KAAM,YACNm6B,MAAO,EACPtrB,UAAW,QAEb,CACE5W,KAAM,oBACND,KAAM,SACNE,MAAO,2BACPQ,QAAS,IAEX,CACET,KAAM,oBACND,KAAM,OACNE,MAAO,oBACPI,YAAa,oBACbI,QAAS,GACTE,KAAM,qCAER,CACEX,KAAM,oBACND,KAAM,OACNE,MAAO,oBACPI,YAAa,uBACbI,QAAS,IAGX,CACEV,KAAM,SACNgI,KAAM,uBACNm6B,MAAO,EACPtrB,UAAW,QAEb,CACE5W,KAAM,mBACND,KAAM,SACNE,MAAO,yBACPQ,QAAS,IAEX,CACET,KAAM,sBACND,KAAM,OACNE,MAAO,sBACPI,YAAa,WACbI,QAAS,IAGX,CACEV,KAAM,SACNgI,KAAM,MACNm6B,MAAO,EACPtrB,UAAW,QAEb,CACE5W,KAAM,WACND,KAAM,SACNE,MAAO,WACPtC,QAAS,CACP,CAAEmK,MAAO,SAAUC,KAAM,yBACzB,CAAED,MAAO,UAAWC,KAAM,4BAE5BD,MAAO,SACPrH,QAAS,IAEX,CACET,KAAM,cACND,KAAM,OACNE,MAAO,kBACPQ,QAAS,GACTE,KAAM,qCAER,CACEX,KAAM,iBACND,KAAM,WACNE,MAAO,qBACPQ,QAAS,IAGX,CACEV,KAAM,SACNgI,KAAM,oBACNm6B,MAAO,EACPtrB,UAAW,QAEb,CACE5W,KAAM,mBACND,KAAM,OACNE,MAAO,kBACPI,YAAa,gDACbI,QAAS,IAEX,CACET,KAAM,sBACND,KAAM,OACNE,MAAO,qBACPI,YAAa,mDACbI,QAAS,IAEX,CACET,KAAM,qBACND,KAAM,OACNE,MAAO,oBACPI,YAAa,kDACbI,QAAS,IAEX,CACET,KAAM,oBACND,KAAM,OACNE,MAAO,mBACPI,YAAa,iDACbI,QAAS,8GAuOU,CACzBb,OAAQ,CACNC,MAAO,qBACPC,OAAQ,CACN,CAAEE,KAAM,OAAQD,KAAM,OAAQE,MAAO,OAAQM,UAAU,EAAMsH,KAAM,IACnE,CAAE7H,KAAM,mBAAoBD,KAAM,OAAQE,MAAO,mBAAoB4H,KAAM,IAC3E,CACE9H,KAAM,SACNC,KAAM,eACN22B,KAAM,CACF,CACI12B,MAAO,OACPH,OAAQ,CACJ,CAAEE,KAAM,gBAAiBD,KAAM,WAAYE,MAAO,gBAAiBqU,KAAM,GAAIzM,KAAM,MAG3F,CACI5H,MAAO,OACPH,OAAQ,CACJ,CAAEE,KAAM,gBAAiBD,KAAM,WAAYE,MAAO,gBAAiBqU,KAAM,GAAIzM,KAAM,UAOrGjH,KAAM,CACFf,MAAO,sBACPC,OAAQ,CACN,CAAEE,KAAM,OAAQD,KAAM,OAAQE,MAAO,OAAQM,UAAU,EAAMsH,KAAM,IACnE,CAAE7H,KAAM,mBAAoBD,KAAM,OAAQE,MAAO,mBAAoB4H,KAAM,IAC3E,CACE9H,KAAM,SACNC,KAAM,eACN22B,KAAM,CACF,CACI12B,MAAO,OACPH,OAAQ,CACJ,CAAEE,KAAM,gBAAiBD,KAAM,WAAYE,MAAO,gBAAiBqU,KAAM,GAAIzM,KAAM,MAG3F,CACI5H,MAAO,OACPH,OAAQ,CACJ,CAAEE,KAAM,gBAAiBD,KAAM,WAAYE,MAAO,gBAAiBqU,KAAM,GAAIzM,KAAM,wFG1cvF,CACdjI,OAAQ,CACJC,MAAO,WACPC,OAAQ,IAKZc,KAAM,CACFf,MAAO,oBACPC,OAAQ,ibCnKM,CAClBF,OAAQ,CACJC,MAAO,kBACPC,OAAQ,CACJ,CACEC,KAAM,SACNC,KAAM,eACN22B,KAAM,CACJ,CACE12B,MAAO,UACPH,OAAQ,CACJ,CACIE,KAAM,QACND,KAAM,OACNE,MAAO,QACPM,UAAU,EACVE,QAAS,IAEb,CACIT,KAAM,UACND,KAAM,WACNE,MAAO,UACPM,UAAU,EACVE,QAAS,MAKnB,CACER,MAAO,WACPH,OAAQ,CACJ,CACIE,KAAM,WACND,KAAM,SACNE,MAAO,WACPtC,QAAS,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,MACvDmK,MAAO,EACPrH,QAAS,GAEb,CACIT,KAAM,SACND,KAAM,SACNE,MAAO,SACP6H,MAAO,OACPnK,QAAS,CAAC,OAAQ,gBAAiB,WAAY,SAAU,SAAU,WACnE8C,QAAS,GAEb,CACIT,KAAM,WACND,KAAM,OACNE,MAAO,WACP6H,MAAO,SACPrH,QAAS,KAInB,CACER,MAAO,WACPH,OAAQ,CACJ,CACIE,KAAM,WACND,KAAM,OACNE,MAAO,WACP6H,MAAO,CAAEq6B,QAAW,eACpB7tB,KAAM,GACN7T,QAAS,UAS/BG,KAAM,CACFf,MAAO,gBACPC,OAAQ,CACJ,CACIE,KAAM,WACND,KAAM,OACNE,MAAO,WACP4H,KAAM,GAEV,CACI7H,KAAM,QACND,KAAM,SACNE,MAAO,QACPI,YAAa,eACb1C,QAAS,CAAC,MAAO,SAAU,SAAU,SAAU,YAC/CkK,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,OACNE,MAAO,YAEX,CACID,KAAM,UACND,KAAM,WACNE,MAAO,cACPI,YAAa,aACbwH,KAAM,IAEV,CACI7H,KAAM,aACND,KAAM,OACNE,MAAO,QACPI,YAAa,cACbwH,KAAM,GAEV,CACI7H,KAAM,WACND,KAAM,OACNE,MAAO,WACPI,YAAa,iBACbwH,KAAM,qaCbL,CACbgG,QAAS,CACLhO,MAAO,kBACPC,OAAQ,CACJ,CACIE,KAAM,OACND,KAAM,OACNE,MAAO,WACPM,UAAU,EACVF,YAAa,wBACbM,KAAM,+BAEV,CACIX,KAAM,UACND,KAAM,OACNE,MAAO,UACP6H,MAAO,UACPnH,KAAM,sCAEV,CACIX,KAAM,UACND,KAAM,WACNE,MAAO,iBACPM,UAAU,EACV+T,KAAM,EACNjU,YAAa,yBACbM,KAAM,wCAEV,CACIX,KAAM,QACND,KAAM,SACNE,MAAO,kBACP8P,IAAK,EACLpP,KAAM,wCAEV,CACIX,KAAM,SACND,KAAM,iBACNE,MAAO,SACPU,KAAM,mCAEV,CACIX,KAAM,cACND,KAAM,SACNE,MAAO,cACP6H,MAAO,EACPiI,IAAK,EACL0gB,IAAK,IAET,CACIzwB,KAAM,aACND,KAAM,SACNE,MAAO,uBACP6H,MAAO,IACPiI,IAAK,GACLpP,KAAM,iDAEV,CACIX,KAAM,YACND,KAAM,SACNE,MAAO,2BACPU,KAAM,sCAKlBgK,MAAO,CACH9K,MAAO,YACPC,OAAQ,CACJ,CACIE,KAAM,QACND,KAAM,SACNE,MAAO,kBACP6H,MAAO,EACPiI,IAAK,EACLpP,KAAM,wCAKlByhC,MAAO,CACHviC,MAAO,YACPC,OAAQ,CACJ,CACIE,KAAM,UACND,KAAM,OACNE,MAAO,UACPU,KAAM,mCAEV,CACIX,KAAM,UACND,KAAM,WACNE,MAAO,0BACPqU,KAAM,EACN3T,KAAM,mCAEV,CACIX,KAAM,QACND,KAAM,SACNE,MAAO,kBACP8P,IAAK,qICrCE,CACnBuC,UAAW,CACPzS,MAAO,oBACPC,OAAQ,CACJ,CACIE,KAAM,UACND,KAAM,SACNE,MAAO,UACPM,UAAU,EACV5C,QAAS,CACL,CAAEmK,MAAO,SAAU7H,MAAO,gBAC1B,CAAE6H,MAAO,QAAS7H,MAAO,oBACzB,CAAE6H,MAAO,SAAU7H,MAAO,qBAC1B,CAAE6H,MAAO,SAAU7H,MAAO,wBAC1B,CAAE6H,MAAO,WAAY7H,MAAO,yBAEhCU,KAAM,kCAEV,CACIX,KAAM,UACND,KAAM,SACNE,MAAO,oBACP6H,MAAO,EACPiI,IAAK,GACL0gB,IAAK,GACL7b,KAAM,GACNjU,KAAM,kXGvRD,CACjBC,KAAM,CACFf,MAAO,2BACPC,OAAQ,CACJ,CAAEE,KAAM,UAAWD,KAAM,OAAQE,MAAO,UAAWQ,QAAQ,IAC3D,CAAET,KAAM,mBAAoBD,KAAM,OAAQE,MAAO,mBAAoBU,KAAM,gCAAiCF,QAAQ,IACpH,CAAET,KAAM,oBAAqBD,KAAM,OAAQE,MAAO,oBAAqBU,KAAM,oBAAqBF,QAAQ,8pBVGhG,CAClBb,OAAQ,CACJC,MAAO,gBACPC,OAAQ,CACJ,CACIE,KAAM,cACND,KAAM,OACNE,MAAO,OACPI,YAAa,cACbM,KAAM,iDACNJ,UAAU,EACVsH,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP4H,KAAM,MAMlBjH,KAAM,CACFf,MAAO,iBACPC,OAAQ,CACJ,CACIE,KAAM,cACND,KAAM,OACNE,MAAO,OACPI,YAAa,cACbM,KAAM,iDACNJ,UAAU,EACVsH,KAAM,IAEV,CACI7H,KAAM,YACND,KAAM,SACNE,MAAO,YACP4H,KAAM,kGC+iBG,CACvBiwB,KAAM,CACJj4B,MAAO,uBACPC,OAAQ,CACN,CAAEE,KAAM,KAAMD,KAAM,OAAQE,MAAO,KAAM0U,UAAU,EAAM9M,KAAM,GAC/D,CAAE7H,KAAM,iBAAkBD,KAAM,OAAQE,MAAO,iBAAkB0U,UAAU,EAAM9M,KAAM,GACvF,CAAE7H,KAAM,aAAcD,KAAM,OAAQE,MAAO,OAAQ0U,UAAU,EAAM9M,KAAM,IACzE,CAAE7H,KAAM,KAAMD,KAAM,WAAYE,MAAO,KAAM0U,UAAU,EAAML,KAAM,EAAGzM,KAAM,IAC5E,CAAE7H,KAAM,KAAMD,KAAM,WAAYE,MAAO,KAAM0U,UAAU,EAAML,KAAM,EAAGzM,KAAM,IAC5E,CAAE7H,KAAM,MAAOD,KAAM,WAAYE,MAAO,MAAO0U,UAAU,EAAML,KAAM,EAAGzM,KAAM,IAC9E,CAAE7H,KAAM,UAAWD,KAAM,OAAQE,MAAO,UAAW0U,UAAU,EAAM9M,KAAM,IACzE,CAAE7H,KAAM,SAAUD,KAAM,OAAQE,MAAO,SAAU0U,UAAU,EAAM9M,KAAM,GACvE,CAAE7H,KAAM,gBAAiBD,KAAM,WAAYE,MAAO,gBAAiB0U,UAAU,EAAML,KAAM,EAAGzM,KAAM,IAClG,CAAE7H,KAAM,UAAWD,KAAM,OAAQE,MAAO,UAAW0U,UAAU,EAAM9M,KAAM"}
|
|
1
|
+
{"version":3,"file":"ChatView-DGulpthL.js","sources":["../../src/core/models/AWS.js","../../src/core/models/Email.js","../../src/core/views/feedback/ProgressView.js","../../src/core/services/FileUpload.js","../../src/core/models/Files.js","../../src/core/models/Incident.js","../../src/core/models/Job.js","../../src/core/models/JobRunner.js","../../src/core/models/Log.js","../../src/core/models/Member.js","../../src/core/models/Metrics.js","../../src/core/models/Push.js","../../src/core/models/System.js","../../src/core/models/Tickets.js","../../src/core/views/table/TableRow.js","../../src/core/utils/DjangoLookups.js","../../src/core/views/table/TableView.js","../../src/core/pages/TablePage.js","../../src/core/views/navigation/TabView.js","../../src/core/views/data/FilePreviewView.js","../../src/core/views/chat/ChatMessageView.js","../../src/core/views/chat/ChatInputView.js","../../src/core/views/chat/ChatView.js"],"sourcesContent":["\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass S3Bucket extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/aws/s3/bucket',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass S3BucketList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: S3Bucket,\n endpoint: '/api/aws/s3/bucket',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst S3BucketForms = {\n create: {\n title: 'Add S3 Bucket',\n fields: [\n {\n name: 'bucket_name',\n type: 'text',\n label: 'Name',\n placeholder: 'bucket name',\n help: 'Enter a universally unique name for the bucket',\n required: true,\n cols: 12,\n },\n {\n name: 'is_public',\n type: 'switch',\n label: 'Is Public',\n cols: 12,\n },\n ],\n },\n\n // Provide an edit form even though legacy only had ADD_FORM\n edit: {\n title: 'Edit S3 Bucket',\n fields: [\n {\n name: 'bucket_name',\n type: 'text',\n label: 'Name',\n placeholder: 'bucket name',\n help: 'Enter a universally unique name for the bucket',\n required: true,\n cols: 12,\n },\n {\n name: 'is_public',\n type: 'switch',\n label: 'Is Public',\n cols: 12,\n },\n ],\n },\n};\n\nexport { S3Bucket, S3BucketList, S3BucketForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\n\n/**\n * EmailDomain - SES/SNS/S3-backed email domain model\n * Maps to REST endpoints under /api/aws/email/domain\n *\n * Key operations:\n * - Create/Update/Delete domains\n * - Onboard: DNS records + SNS + optional receiving\n * - Audit: Drift report (verification/DKIM, topics, receipt rules)\n * - Reconcile: Safe, idempotent fixes (no DNS writes)\n *\n * Notes:\n * - Management endpoints require \"manage_aws\" permission server-side.\n * - Error handling follows the MOJO Rest response contract.\n */\nclass EmailDomain extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/domain',\n ...options\n });\n }\n\n /**\n * Onboard the domain (DNS/SNS/receiving orchestration).\n * POST /api/aws/email/domain/<id>/onboard\n *\n * @param {object} data\n * receiving_enabled?: boolean\n * s3_inbound_bucket?: string\n * s3_inbound_prefix?: string\n * ensure_mail_from?: boolean\n * mail_from_subdomain?: string\n * dns_mode?: \"manual\" | \"godaddy\"\n * godaddy_key?: string\n * godaddy_secret?: string\n * endpoints?: { bounce, complaint, delivery, inbound }\n * @param {object} options - Optional { params?: object }\n * @returns {Promise<object>} REST response\n */\n async onboard(data = {}, options = {}) {\n if (!this.id) {\n await this.showError('Cannot onboard domain without ID');\n return {\n success: false,\n status: 400,\n error: 'Missing domain id'\n };\n }\n\n try {\n const url = `${this.buildUrl(this.id)}/onboard`;\n const response = await this.rest.POST(url, data, options.params);\n\n // No guaranteed shape to merge; response contains DNS records, topics, notes, etc.\n // If backend returns updated domain fields, you can call this.set(response.data.data).\n return response;\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to onboard domain'\n };\n }\n }\n\n /**\n * Audit domain configuration for drift.\n * GET or POST /api/aws/email/domain/<id>/audit\n *\n * @param {object} options\n * method?: 'GET'|'POST' (default 'GET')\n * data?: object (when method is POST)\n * params?: object (query params for GET)\n * @returns {Promise<object>} REST response\n */\n async audit(options = {}) {\n if (!this.id) {\n await this.showError('Cannot audit domain without ID');\n return {\n success: false,\n status: 400,\n error: 'Missing domain id'\n };\n }\n\n const method = (options.method || 'GET').toUpperCase();\n const url = `${this.buildUrl(this.id)}/audit`;\n\n try {\n if (method === 'POST') {\n return await this.rest.POST(url, options.data || {}, options.params);\n }\n return await this.rest.GET(url, options.params);\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to audit domain'\n };\n }\n }\n\n /**\n * Reconcile domain configuration (safe fixes; no DNS writes).\n * POST /api/aws/email/domain/<id>/reconcile\n *\n * @param {object} data - Optional payload (usually none required)\n * @param {object} options - Optional { params?: object }\n * @returns {Promise<object>} REST response\n */\n async reconcile(data = {}, options = {}) {\n if (!this.id) {\n await this.showError('Cannot reconcile domain without ID');\n return {\n success: false,\n status: 400,\n error: 'Missing domain id'\n };\n }\n\n try {\n const url = `${this.buildUrl(this.id)}/reconcile`;\n return await this.rest.POST(url, data, options.params);\n } catch (err) {\n return {\n success: false,\n status: err?.status || 500,\n error: err?.message || 'Failed to reconcile domain'\n };\n }\n }\n\n /**\n * Convenience: Onboard by id without creating a separate instance.\n * @param {string|number} id\n * @param {object} data\n * @param {object} options\n */\n static async onboardById(id, data = {}, options = {}) {\n const model = new EmailDomain({ id }, options);\n return await model.onboard(data, options);\n }\n\n /**\n * Convenience: Audit by id without creating a separate instance.\n * @param {string|number} id\n * @param {object} options - see audit()\n */\n static async auditById(id, options = {}) {\n const model = new EmailDomain({ id }, options);\n return await model.audit(options);\n }\n\n /**\n * Convenience: Reconcile by id without creating a separate instance.\n * @param {string|number} id\n * @param {object} data\n * @param {object} options\n */\n static async reconcileById(id, data = {}, options = {}) {\n const model = new EmailDomain({ id }, options);\n return await model.reconcile(data, options);\n }\n}\n\n/**\n * EmailDomainList - Collection of EmailDomain\n * Supports standard MOJO list/search/sort/pagination patterns\n */\nclass EmailDomainList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: EmailDomain,\n endpoint: '/api/aws/email/domain',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for EmailDomain (for TablePage/Dialog integration)\n * NOTE: Conditional requirements (e.g., s3_inbound_bucket when receiving_enabled)\n * should be validated server-side; help text guides the admin.\n */\nconst EmailDomainForms = {\n create: {\n title: 'Add Email Domain',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Domain Name',\n placeholder: 'example.com',\n required: true,\n columns: 12,\n help: 'Enter the root domain to verify with SES (no protocol).'\n },\n {\n name: 'region',\n type: 'text',\n label: 'AWS Region (optional)',\n placeholder: 'us-east-1',\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with SES permissions',\n columns: 12,\n help: 'Optional, AWS Key with SES permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with SES permissions',\n columns: 12,\n help: 'Optional, AWS Secret with SES permissions'\n },\n {\n name: 'receiving_enabled',\n type: 'switch',\n label: 'Enable Inbound Receiving',\n columns: 12,\n help: 'Catch-all SES receipt rule to S3 + SNS; routing is done in-app.'\n }\n ]\n },\n\n edit: {\n title: 'Edit Email Domain',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Domain Name',\n placeholder: 'example.com',\n required: true,\n columns: 12,\n readonly: true,\n help: 'Domain name cannot be changed after creation.'\n },\n {\n name: 'region',\n type: 'text',\n label: 'AWS Region',\n placeholder: 'us-east-1',\n columns: 12\n },\n {\n name: 'receiving_enabled',\n type: 'switch',\n label: 'Enable Inbound Receiving',\n columns: 12\n },\n {\n name: 's3_inbound_bucket',\n type: 'text',\n label: 'Inbound S3 Bucket',\n placeholder: 'my-inbound-bucket',\n columns: 12\n },\n {\n name: 's3_inbound_prefix',\n type: 'text',\n label: 'Inbound S3 Prefix',\n placeholder: 'inbound/example.com/',\n columns: 12\n },\n {\n name: 'dns_mode',\n type: 'select',\n label: 'DNS Mode',\n options: [\n { value: 'manual', text: 'Manual (show records)' },\n { value: 'godaddy', text: 'GoDaddy (apply via API)' }\n ],\n columns: 12\n }\n ],\n },\n\n credentials: {\n fields: [\n {\n name: 'region',\n type: 'select',\n label: 'AWS Region (optional)',\n placeholder: 'us-east-1',\n options: [\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with SES permissions',\n columns: 12,\n help: 'Optional, AWS Key with SES permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with SES permissions',\n columns: 12,\n help: 'Optional, AWS Secret with SES permissions'\n },\n ]\n},\n\n onboard: {\n title: 'Onboard Domain',\n fields: [\n {\n type: 'header',\n text: 'Receiving',\n level: 6,\n className: 'mt-2'\n },\n {\n name: 'receiving_enabled',\n type: 'switch',\n label: 'Enable Inbound Receiving',\n columns: 12\n },\n {\n name: 's3_inbound_bucket',\n type: 'text',\n label: 'Inbound S3 Bucket',\n placeholder: 'my-inbound-bucket',\n columns: 12,\n help: 'Required if receiving is enabled.'\n },\n {\n name: 's3_inbound_prefix',\n type: 'text',\n label: 'Inbound S3 Prefix',\n placeholder: 'inbound/example.com/',\n columns: 12\n },\n\n {\n type: 'header',\n text: 'MAIL FROM (optional)',\n level: 6,\n className: 'mt-3'\n },\n {\n name: 'ensure_mail_from',\n type: 'switch',\n label: 'Ensure MAIL FROM Setup',\n columns: 12\n },\n {\n name: 'mail_from_subdomain',\n type: 'text',\n label: 'MAIL FROM Subdomain',\n placeholder: 'feedback',\n columns: 12\n },\n\n {\n type: 'header',\n text: 'DNS',\n level: 6,\n className: 'mt-3'\n },\n {\n name: 'dns_mode',\n type: 'select',\n label: 'DNS Mode',\n options: [\n { value: 'manual', text: 'Manual (show records)' },\n { value: 'godaddy', text: 'GoDaddy (apply via API)' }\n ],\n value: 'manual',\n columns: 12\n },\n {\n name: 'godaddy_key',\n type: 'text',\n label: 'GoDaddy API Key',\n columns: 12,\n help: 'Required when DNS Mode = GoDaddy.'\n },\n {\n name: 'godaddy_secret',\n type: 'password',\n label: 'GoDaddy API Secret',\n columns: 12\n },\n\n {\n type: 'header',\n text: 'Webhook Endpoints',\n level: 6,\n className: 'mt-3'\n },\n {\n name: 'endpoints.bounce',\n type: 'text',\n label: 'Bounce Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/bounce',\n columns: 12\n },\n {\n name: 'endpoints.complaint',\n type: 'text',\n label: 'Complaint Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/complaint',\n columns: 12\n },\n {\n name: 'endpoints.delivery',\n type: 'text',\n label: 'Delivery Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/delivery',\n columns: 12\n },\n {\n name: 'endpoints.inbound',\n type: 'text',\n label: 'Inbound Endpoint',\n placeholder: 'https://portal.example.com/api/aws/sns/inbound',\n columns: 12\n }\n ]\n }\n};\n\n/**\n * Mailbox - Represents a single email address within a domain.\n * Endpoint: /api/aws/email/mailbox\n * Fields (typical):\n * - domain: FK (domain id) or name (server may accept name)\n * - email: full address\n * - allow_inbound: boolean\n * - allow_outbound: boolean\n * - async_handler: \"package.module:function\" for task dispatch on inbound\n */\nclass Mailbox extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/mailbox',\n ...options\n });\n }\n}\n\nMailbox.sendEmail = async function(data) {\n return await rest.POST('/api/aws/email/send', data);\n};\n\n/**\n * MailboxList - Collection of Mailboxes\n */\nclass MailboxList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Mailbox,\n endpoint: '/api/aws/email/mailbox',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for Mailbox CRUD\n */\nconst MailboxForms = {\n create: {\n title: 'Add Mailbox',\n fields: [\n {\n type: 'collection',\n name: 'domain',\n label: 'Domain',\n Collection: EmailDomainList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search domains...',\n emptyFetch: false,\n required: true,\n debounceMs: 300, // Search debounce delay\n columns: 12\n },\n {\n name: 'email',\n type: 'email',\n label: 'Email Address',\n placeholder: 'support@example.com',\n required: true,\n columns: 12\n },\n {\n name: 'allow_inbound',\n type: 'switch',\n label: 'Allow Inbound',\n columns: 6\n },\n {\n name: 'allow_outbound',\n type: 'switch',\n label: 'Allow Outbound',\n defaultValue: true,\n columns: 6\n },\n {\n name: 'is_system_default',\n type: 'switch',\n label: 'System Default',\n columns: 6\n },\n {\n name: 'is_domain_default',\n type: 'switch',\n label: 'Domain Default',\n columns: 6\n },\n {\n name: 'async_handler',\n type: 'text',\n label: 'Async Handler (optional)',\n placeholder: 'myapp.handlers.process_support',\n columns: 12,\n help: 'Module:function to process inbound messages via task system'\n }\n ]\n },\n\n edit: {\n title: 'Edit Mailbox',\n fields: [\n {\n name: 'email',\n type: 'email',\n label: 'Email Address',\n required: true,\n columns: 12\n },\n {\n name: 'allow_inbound',\n type: 'switch',\n label: 'Allow Inbound',\n columns: 6\n },\n {\n name: 'allow_outbound',\n type: 'switch',\n label: 'Allow Outbound',\n columns: 6\n },\n {\n name: 'is_system_default',\n type: 'switch',\n label: 'System Default',\n columns: 6\n },\n {\n name: 'is_domain_default',\n type: 'switch',\n label: 'Domain Default',\n columns: 6\n },\n {\n name: 'async_handler',\n type: 'text',\n label: 'Async Handler (optional)',\n placeholder: 'myapp.handlers.process_support',\n columns: 12\n }\n ]\n }\n};\n\n/**\n * SentMessage - Outbound messages sent via SES; read-only in UI\n * Endpoint: /api/aws/email/sent\n */\nclass SentMessage extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/sent',\n ...options\n });\n }\n}\n\n/**\n * SentMessageList - Collection of SentMessage\n */\nclass SentMessageList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: SentMessage,\n endpoint: '/api/aws/email/sent',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms for SentMessage (read-only details)\n */\nconst SentMessageForms = {\n view: {\n title: 'Sent Message Details',\n fields: [\n { name: 'id', type: 'text', label: 'ID', readonly: true, cols: 6 },\n { name: 'ses_message_id', type: 'text', label: 'SES Message ID', readonly: true, cols: 6 },\n { name: 'from_email', type: 'text', label: 'From', readonly: true, cols: 12 },\n { name: 'to', type: 'textarea', label: 'To', readonly: true, rows: 2, cols: 12 },\n { name: 'cc', type: 'textarea', label: 'CC', readonly: true, rows: 2, cols: 12 },\n { name: 'bcc', type: 'textarea', label: 'BCC', readonly: true, rows: 2, cols: 12 },\n { name: 'subject', type: 'text', label: 'Subject', readonly: true, cols: 12 },\n { name: 'status', type: 'text', label: 'Status', readonly: true, cols: 6 },\n { name: 'status_reason', type: 'textarea', label: 'Status Reason', readonly: true, rows: 3, cols: 12 },\n { name: 'created', type: 'text', label: 'Created', readonly: true, cols: 6 }\n ]\n }\n};\n\n/**\n * EmailTemplate - DB template entries (Django templated), CRUD supported\n * Endpoint: /api/aws/email/template\n */\nclass EmailTemplate extends Model {\n constructor(data = {}, options = {}) {\n super(data, {\n endpoint: '/api/aws/email/template',\n ...options\n });\n }\n}\n\n/**\n * EmailTemplateList - Collection of EmailTemplate\n */\nclass EmailTemplateList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: EmailTemplate,\n endpoint: '/api/aws/email/template',\n size: 10,\n ...options\n });\n }\n}\n\n/**\n * Forms configuration for EmailTemplate CRUD\n */\nconst EmailTemplateForms = {\n create: {\n title: 'Add Email Template',\n fields: [\n { name: 'name', type: 'text', label: 'Name', required: true, cols: 12 },\n { name: 'subject_template', type: 'text', label: 'Subject Template', cols: 12 },\n {\n type: 'tabset',\n name: 'settingsTabs',\n tabs: [\n {\n label: 'HTML',\n fields: [\n { name: 'html_template', type: 'textarea', label: 'HTML Template', rows: 16, cols: 12 },\n ],\n },\n {\n label: 'TEXT',\n fields: [\n { name: 'text_template', type: 'textarea', label: 'Text Template', rows: 16, cols: 12 }\n ]\n }\n ]\n }\n ]\n },\n edit: {\n title: 'Edit Email Template',\n fields: [\n { name: 'name', type: 'text', label: 'Name', required: true, cols: 12 },\n { name: 'subject_template', type: 'text', label: 'Subject Template', cols: 12 },\n {\n type: 'tabset',\n name: 'settingsTabs',\n tabs: [\n {\n label: 'HTML',\n fields: [\n { name: 'html_template', type: 'textarea', label: 'HTML Template', rows: 16, cols: 12 },\n ],\n },\n {\n label: 'TEXT',\n fields: [\n { name: 'text_template', type: 'textarea', label: 'Text Template', rows: 16, cols: 12 }\n ]\n }\n ]\n }\n ]\n }\n};\n\nexport {\n EmailDomain,\n EmailDomainList,\n EmailDomainForms,\n Mailbox,\n MailboxList,\n MailboxForms,\n SentMessage,\n SentMessageList,\n SentMessageForms,\n EmailTemplate,\n EmailTemplateList,\n EmailTemplateForms\n};\n","/**\n * ProgressView - File upload progress component\n * \n * Shows upload progress with progress bar, filename, and cancellation option\n * Integrates with FileUpload service for real-time progress updates\n * \n * Features:\n * - Bootstrap progress bar with percentage\n * - File information (name, size)\n * - Bytes uploaded/total display\n * - Cancel button with confirmation\n * - Responsive design\n * \n * Events:\n * - 'cancel' - Emitted when user cancels upload\n * \n * @example\n * const progressView = new ProgressView({\n * filename: 'document.pdf',\n * filesize: 1024000,\n * onCancel: () => fileUpload.cancel()\n * });\n * \n * // Update progress\n * progressView.updateProgress({ progress: 0.5, loaded: 512000, total: 1024000 });\n */\n\nimport View from '@core/View.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass ProgressView extends View {\n constructor(options = {}) {\n super({\n template: 'progress-view-template',\n ...options\n });\n\n // Initialize progress data\n this.filename = options.filename || 'Unknown file';\n this.filesize = options.filesize || 0;\n this.filesizeFormatted = dataFormatter.pipe(this.filesize, 'filesize');\n \n // Progress state\n this.progress = 0;\n this.percentage = 0;\n this.loaded = 0;\n this.total = this.filesize;\n this.loadedFormatted = '0 B';\n this.totalFormatted = this.filesizeFormatted;\n this.status = 'Starting upload...';\n \n // Options\n this.showCancel = options.showCancel !== false;\n this.onCancel = options.onCancel || null;\n \n // State\n this.cancelled = false;\n this.completed = false;\n }\n\n /**\n * Get template for the progress view\n */\n getTemplate() {\n return `\n <div class=\"progress-view\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <div class=\"flex-grow-1 min-width-0\">\n <div class=\"fw-medium text-truncate\" title=\"{{filename}}\">\n <i class=\"bi bi-file-earmark me-1\"></i>\n {{filename}}\n </div>\n <small class=\"text-muted\">{{status}}</small>\n </div>\n {{#showCancel}}\n <button type=\"button\" \n class=\"btn btn-sm btn-outline-secondary ms-2\" \n data-action=\"cancel\"\n {{#cancelled}}disabled{{/cancelled}}>\n <i class=\"bi bi-x\"></i>\n </button>\n {{/showCancel}}\n </div>\n \n <div class=\"progress mb-2\" style=\"height: 8px;\">\n <div class=\"progress-bar\" \n role=\"progressbar\" \n style=\"width: {{percentage}}%\"\n aria-valuenow=\"{{percentage}}\" \n aria-valuemin=\"0\" \n aria-valuemax=\"100\">\n </div>\n </div>\n \n <div class=\"d-flex justify-content-between\">\n <small class=\"text-muted\">\n {{loadedFormatted}} / {{totalFormatted}}\n </small>\n <small class=\"text-muted\">\n {{percentage}}%\n </small>\n </div>\n </div>\n `;\n }\n\n /**\n * Update progress information\n * @param {Object} progressInfo - Progress data\n * @param {number} progressInfo.progress - Progress as decimal (0-1)\n * @param {number} progressInfo.loaded - Bytes loaded\n * @param {number} progressInfo.total - Total bytes\n * @param {number} progressInfo.percentage - Progress as percentage (0-100)\n */\n updateProgress(progressInfo) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n this.progress = progressInfo.progress;\n this.percentage = progressInfo.percentage;\n this.loaded = progressInfo.loaded;\n this.total = progressInfo.total || this.filesize;\n \n // Format bytes for display\n this.loadedFormatted = dataFormatter.pipe(this.loaded, 'filesize');\n this.totalFormatted = dataFormatter.pipe(this.total, 'filesize');\n \n // Update status message\n if (this.percentage < 100) {\n this.status = `Uploading... ${this.percentage}%`;\n } else {\n this.status = 'Finalizing upload...';\n }\n\n // Re-render to show updated progress\n this.render();\n }\n\n /**\n * Mark upload as completed\n * @param {string} message - Success message\n */\n markCompleted(message = 'Upload completed!') {\n this.completed = true;\n this.progress = 1;\n this.percentage = 100;\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as failed\n * @param {string} message - Error message\n */\n markFailed(message = 'Upload failed') {\n this.status = message;\n this.render();\n }\n\n /**\n * Mark upload as cancelled\n */\n markCancelled() {\n this.cancelled = true;\n this.status = 'Upload cancelled';\n this.render();\n }\n\n /**\n * Handle cancel button click\n * @param {string} action - Action name\n * @param {Event} event - Click event\n * @param {Element} element - Button element\n */\n async onActionCancel(action, event, element) {\n if (this.cancelled || this.completed) {\n return;\n }\n\n // Disable button immediately\n element.disabled = true;\n \n // Mark as cancelled\n this.markCancelled();\n \n // Emit cancel event\n this.emit('cancel');\n \n // Call cancel callback if provided\n if (typeof this.onCancel === 'function') {\n try {\n await this.onCancel();\n } catch (error) {\n console.error('Error in cancel callback:', error);\n }\n }\n }\n\n /**\n * Set filename\n * @param {string} filename - New filename\n */\n setFilename(filename) {\n this.filename = filename;\n this.render();\n }\n\n /**\n * Set file size\n * @param {number} size - File size in bytes\n */\n setFilesize(size) {\n this.filesize = size;\n this.filesizeFormatted = dataFormatter.pipe(size, 'filesize');\n this.total = size;\n this.totalFormatted = this.filesizeFormatted;\n this.render();\n }\n\n /**\n * Get current progress as percentage\n * @returns {number} Progress percentage (0-100)\n */\n getPercentage() {\n return this.percentage;\n }\n\n /**\n * Check if upload is completed\n * @returns {boolean} True if completed\n */\n isCompleted() {\n return this.completed;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.filename,\n filesize: this.filesize,\n progress: this.progress,\n percentage: this.percentage,\n loaded: this.loaded,\n total: this.total,\n cancelled: this.cancelled,\n completed: this.completed,\n status: this.status\n };\n }\n}\n\nexport default ProgressView;","/**\n * FileUpload - File upload service with progress tracking and UI integration\n *\n * Features:\n * - Auto-start upload process\n * - Progress tracking with detailed information\n * - Promise interface with cancellation support\n * - Toast integration for user feedback\n * - Three-stage upload process (initiate → upload → complete)\n *\n * @example\n * const file = new File();\n * const upload = file.upload({\n * file: fileObject,\n * name: 'avatar.jpg',\n * group: 'profile-pics',\n * description: 'User avatar',\n * onProgress: ({ progress, loaded, total, percentage }) => {\n * console.log(`${percentage}% complete`);\n * }\n * });\n *\n * upload.then(result => console.log('Success!'))\n * .catch(error => console.error('Failed:', error));\n */\n\nimport ToastService from './ToastService.js';\nimport ProgressView from '../views/feedback/ProgressView.js';\n\nclass FileUpload {\n constructor(fileModel, options = {}) {\n this.fileModel = fileModel;\n this.options = {\n file: null,\n name: null,\n group: null,\n description: null,\n onProgress: null,\n onComplete: null,\n onError: null,\n showToast: true,\n ...options\n };\n\n // Validation\n if (!this.options.file || !(this.options.file instanceof File)) {\n throw new Error('FileUpload requires a valid File object');\n }\n\n // State management\n this.cancelled = false;\n this.uploadRequest = null;\n this.progressToast = null;\n this.progressView = null;\n this.toastService = null;\n\n // Initialize toast service if needed\n if (this.options.showToast) {\n this.toastService = new ToastService();\n }\n\n // Auto-start upload (Option 3 behavior)\n this.promise = this._startUpload();\n }\n\n /**\n * Main upload orchestration\n * @returns {Promise} Upload promise\n * @private\n */\n async _startUpload() {\n try {\n if (this.options.showToast) {\n this._showProgressToast();\n }\n\n // Stage 1: Initiate upload and get signed URL\n let uploadData;\n try {\n uploadData = await this._initiateUpload();\n } catch (error) {\n throw new Error(`Failed to initiate upload: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Validate upload data\n if (!uploadData || !uploadData.upload_url) {\n throw new Error('Invalid upload response: missing upload URL');\n }\n\n // Stage 2: Upload file to signed URL\n let result;\n try {\n result = await this._performUpload(uploadData.upload_url);\n } catch (error) {\n throw new Error(`File upload failed: ${error.message}`);\n }\n\n if (this.cancelled) {\n throw new Error('Upload cancelled');\n }\n\n // Stage 3: Mark upload as completed\n try {\n await this._completeUpload();\n } catch (error) {\n console.warn('Failed to mark upload as completed:', error);\n // Don't fail the entire upload for completion marking errors\n // The file was successfully uploaded\n }\n\n // Handle success\n this._onComplete(this.fileModel);\n return this.fileModel;\n\n } catch (error) {\n if (error.message !== 'Upload cancelled') {\n this._onError(error);\n }\n throw error;\n }\n }\n\n /**\n * Initiate upload by calling the API to get signed URL\n * @returns {Promise<Object>} Upload initiation data\n * @private\n */\n async _initiateUpload() {\n try {\n const payload = {\n filename: this.options.name || this.options.file.name,\n file_size: this.options.file.size,\n content_type: this.options.file.type,\n };\n\n if (this.options.group) payload.group = this.options.group;\n if (this.options.description) payload.description = this.options.description;\n\n const response = await this.fileModel.rest.POST('/api/fileman/upload/initiate', payload);\n\n if (!response) {\n throw new Error('No response from upload initiation API');\n }\n\n if (!response.data) {\n throw new Error('Upload initiation response missing data');\n }\n\n // Check server response first (prefer server error messages)\n if (!response.data.status) {\n const errorMessage = response.data.error || 'Upload initiation failed';\n throw new Error(errorMessage);\n }\n\n if (!response.data.data) {\n throw new Error('Upload initiation response missing data payload');\n }\n\n // Set model ID for completion step\n if (response.data.data.id) {\n this.fileModel.set('id', response.data.data.id);\n }\n\n return response.data.data; // { id, upload_url }\n\n } catch (error) {\n // Re-throw with more context if it's a generic error\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload initiation. Please check your connection.');\n }\n throw error;\n }\n }\n\n /**\n * Upload file to signed URL with direct XHR control for cancellation\n * @param {string} uploadUrl - Signed upload URL\n * @returns {Promise} Upload result\n * @private\n */\n async _performUpload(uploadUrl) {\n return new Promise((resolve, reject) => {\n // Validate input\n if (!(this.options.file instanceof File)) {\n reject(new Error('Only single File objects are supported'));\n return;\n }\n\n const xhr = new XMLHttpRequest();\n this.uploadRequest = xhr; // Store XHR for cancellation\n\n // Set up progress tracking\n xhr.upload.onprogress = (event) => {\n if (this.cancelled) return;\n\n const progressInfo = {\n progress: event.loaded / event.total,\n loaded: event.loaded,\n total: event.total,\n percentage: Math.round((event.loaded / event.total) * 100)\n };\n\n this._onProgress(progressInfo);\n };\n\n // Set up response handlers\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve({\n data: xhr.response,\n status: xhr.status,\n statusText: xhr.statusText,\n xhr: xhr\n });\n } else {\n reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));\n }\n };\n\n xhr.onerror = () => {\n reject(new Error('Upload failed: Network error'));\n };\n\n xhr.ontimeout = () => {\n reject(new Error('Upload failed: Timeout'));\n };\n\n xhr.onabort = () => {\n reject(new Error('Upload cancelled'));\n };\n\n xhr.ontimeout = () => {\n reject(new Error('Upload timeout - file may be too large or connection too slow'));\n };\n\n // Configure request - use PUT method with raw file data\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', this.options.file.type);\n\n // Set timeout if specified (30 seconds default)\n xhr.timeout = 30000;\n\n // Send the raw file data\n xhr.send(this.options.file);\n });\n }\n\n /**\n * Mark upload as completed in the API\n * @returns {Promise} Completion result\n * @private\n */\n async _completeUpload() {\n try {\n const response = await this.fileModel.save({ action: 'mark_as_completed' });\n\n if (!response) {\n throw new Error('No response from upload completion API');\n }\n\n // Check server response format (prefer server errors over HTTP errors)\n if (response.data && !response.data.status) {\n const errorMessage = response.data.error || 'Failed to mark upload as completed';\n throw new Error(errorMessage);\n }\n\n return response;\n } catch (error) {\n // Re-throw with more context\n if (error.message === 'Network Error' || error.name === 'TypeError') {\n throw new Error('Network error during upload completion. The file may have uploaded successfully.');\n }\n throw error;\n }\n }\n\n /**\n * Handle progress updates\n * @param {Object} progressInfo - Progress information\n * @private\n */\n _onProgress(progressInfo) {\n // Update progress toast if shown\n if (this.progressToast && this.progressToast.updateProgress) {\n this.progressToast.updateProgress(progressInfo);\n }\n\n // Call user-provided progress callback\n if (typeof this.options.onProgress === 'function') {\n this.options.onProgress(progressInfo);\n }\n }\n\n /**\n * Handle successful upload completion\n * @param {Object} result - Upload result\n * @private\n */\n _onComplete(result) {\n // Mark progress view as completed\n if (this.progressView) {\n this.progressView.markCompleted('Upload completed successfully!');\n }\n\n // Auto-hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast:', error);\n }\n }, 2000);\n }\n\n // Call user-provided completion callback\n if (typeof this.options.onComplete === 'function') {\n this.options.onComplete(result);\n }\n }\n\n /**\n * Handle upload errors\n * @param {Error} error - Error object\n * @private\n */\n _onError(error) {\n // Hide progress toast immediately and show error toast\n if (this.progressToast) {\n try {\n this.progressToast.hide();\n } catch (error) {\n console.warn('Error hiding progress toast on error:', error);\n }\n }\n\n // Show error toast with immediate feedback\n if (this.toastService) {\n this.toastService.error(`Upload failed: ${error.message}`);\n }\n\n // Call user-provided error callback\n if (typeof this.options.onError === 'function') {\n this.options.onError(error);\n }\n }\n\n /**\n * Show progress toast with ProgressView component\n * @private\n */\n _showProgressToast() {\n // Create progress view with file information\n this.progressView = new ProgressView({\n filename: this.options.name || this.options.file.name,\n filesize: this.options.file.size,\n showCancel: true,\n onCancel: () => this.cancel()\n });\n\n // Show progress view in toast\n this.progressToast = this.toastService.showView(this.progressView, 'info', {\n title: 'File Upload',\n autohide: false,\n dismissible: false\n });\n }\n\n /**\n * Cancel the upload\n * @returns {boolean} True if cancelled, false if already completed\n */\n cancel() {\n if (this.cancelled) {\n return false;\n }\n\n this.cancelled = true;\n\n // Cancel the upload request if in progress\n if (this.uploadRequest && typeof this.uploadRequest.abort === 'function') {\n this.uploadRequest.abort();\n }\n\n // Mark progress view as cancelled\n if (this.progressView) {\n this.progressView.markCancelled();\n }\n\n // Hide progress toast after a delay\n if (this.progressToast) {\n setTimeout(() => {\n try {\n if (this.progressToast && typeof this.progressToast.hide === 'function') {\n this.progressToast.hide();\n }\n } catch (error) {\n console.warn('Error hiding progress toast on cancel:', error);\n }\n }, 1500);\n }\n\n return true;\n }\n\n /**\n * Check if upload is cancelled\n * @returns {boolean} True if cancelled\n */\n isCancelled() {\n return this.cancelled;\n }\n\n /**\n * Promise interface - then\n * @param {function} onSuccess - Success handler\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n then(onSuccess, onError) {\n return this.promise.then(onSuccess, onError);\n }\n\n /**\n * Promise interface - catch\n * @param {function} onError - Error handler\n * @returns {Promise} Promise chain\n */\n catch(onError) {\n return this.promise.catch(onError);\n }\n\n /**\n * Promise interface - finally\n * @param {function} onFinally - Finally handler\n * @returns {Promise} Promise chain\n */\n finally(onFinally) {\n return this.promise.finally(onFinally);\n }\n\n /**\n * Get upload statistics\n * @returns {Object} Upload stats\n */\n getStats() {\n return {\n filename: this.options.file.name,\n size: this.options.file.size,\n type: this.options.file.type,\n cancelled: this.cancelled,\n group: this.options.group,\n description: this.options.description\n };\n }\n}\n\nexport default FileUpload;\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport FileUpload from '@core/services/FileUpload.js';\nimport {UserList} from '@core/models/User.js';\nimport {GroupList} from '@core/models/Group.js';\n/* =========================\n * FileManager\n * ========================= */\nclass FileManager extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/manager',\n });\n }\n}\n\nclass FileManagerList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: FileManager,\n endpoint: '/api/fileman/manager',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileManagerForms = {\n create: {\n title: 'Add Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n value: \"s3://BUCKET_NAME/OPTION_FOLDER\",\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n },\n ],\n },\n\n edit: {\n title: 'Edit Storage Backend',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name',\n cols: 12,\n },\n {\n name: 'backend_url',\n type: 'text',\n label: 'Backend URL',\n required: true,\n placeholder: 's3://bucket_name/optional folder',\n help: 'Format: service://path. Valid services: s3',\n cols: 12,\n },\n {\n name: 'allowed_origins',\n type: 'text',\n label: 'Domains Who Can Upload',\n cols: 12,\n },\n {\n name: 'is_default',\n type: 'switch',\n label: 'Is Default',\n cols: 6,\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n default: true,\n cols: 6,\n }\n ],\n },\n\n owners: {\n fields: [\n {\n type: 'collection',\n name: 'group',\n label: 'Group (Owner)',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n {\n type: 'collection',\n name: 'user',\n label: 'User (Owner)',\n Collection: UserList, // Collection class\n labelField: 'display_name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search users...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n ],\n },\n\n credentials: {\n fields: [\n {\n name: 'aws_region',\n type: 'select',\n label: 'AWS Region (optional)',\n value: 'us-east-1',\n options: [\n { value: '', text: 'System Default' },\n { value: 'us-east-1', text: 'US East (N. Virginia)' },\n { value: 'us-east-2', text: 'US East (Ohio)' },\n { value: 'us-west-1', text: 'US West (N. California)' },\n { value: 'us-west-2', text: 'US West (Oregon)' },\n { value: 'ca-central-1', text: 'Canada (Central)' },\n { value: 'eu-west-1', text: 'Europe (Ireland)' },\n { value: 'eu-west-2', text: 'Europe (London)' },\n { value: 'eu-west-3', text: 'Europe (Paris)' },\n { value: 'eu-central-1', text: 'Europe (Frankfurt)' },\n { value: 'eu-north-1', text: 'Europe (Stockholm)' },\n { value: 'eu-south-1', text: 'Europe (Milan)' },\n { value: 'ap-southeast-2', text: 'Asia Pacific (Sydney)' }\n ],\n columns: 12,\n help: 'Optional. Defaults to project AWS_REGION if omitted.'\n },\n {\n name: 'aws_key',\n type: 'text',\n label: 'AWS Key (optional)',\n placeholder: 'enter your AWS Key with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Key with S3 permissions'\n },\n {\n name: 'aws_secret',\n type: 'text',\n label: 'AWS Secret (optional)',\n placeholder: 'enter your AWS Secret with S3 permissions',\n columns: 12,\n help: 'Optional, AWS Secret with S3 permissions'\n },\n ]\n }\n};\n\n/* =========================\n * File\n * ========================= */\nclass File extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/fileman/file',\n });\n }\n\n isImage() {\n return this.get(\"category\") === 'image';\n }\n\n /**\n * Upload file with progress tracking and UI integration\n * Returns a FileUpload instance with promise interface and cancellation support\n *\n * @param {object} options - Upload configuration\n * @param {File} options.file - File object to upload\n * @param {string} options.name - Custom filename (optional)\n * @param {string} options.group - File group/category (optional)\n * @param {string} options.description - File description (optional)\n * @param {function} options.onProgress - Progress callback ({ progress, loaded, total, percentage })\n * @param {function} options.onComplete - Success callback\n * @param {function} options.onError - Error callback\n * @param {boolean} options.showToast - Show progress toast (default: true)\n * @returns {FileUpload} Upload instance with promise interface\n */\n upload(options = {}) {\n return new FileUpload(this, options);\n }\n}\n\nclass FileList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: File,\n endpoint: '/api/fileman/file',\n size: 10,\n ...options,\n });\n }\n}\n\nconst FileForms = {\n create: {\n title: 'Add File',\n fields: [\n\n ],\n },\n\n edit: {\n title: 'Edit File Backend',\n fields: [\n\n ],\n },\n};\n\nexport {\n FileManager,\n FileManagerList,\n FileManagerForms,\n File,\n FileList,\n FileForms,\n};\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * IncidentEvent\n * ========================= */\n class IncidentStats extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/stats',\n requiresId: false\n });\n }\n }\n\n\nclass IncidentEvent extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event',\n });\n }\n}\n\nclass IncidentEventList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentEvent,\n endpoint: '/api/incident/event',\n size: 10,\n ...options,\n });\n }\n}\n\nconst IncidentEventForms = {\n edit: {\n title: 'Edit Incident Event',\n fields: [\n {\n name: 'category',\n type: 'select',\n label: 'Category',\n placeholder: 'Select category',\n options: () => Incident.COMPONENTS,\n editable: true,\n force_top: true,\n cols: 6,\n },\n {\n name: 'incident',\n type: 'text',\n label: 'Incident',\n placeholder: 'Enter Incident ID',\n cols: 6,\n },\n {\n name: 'description',\n type: 'textarea',\n label: 'Description',\n placeholder: 'Enter Description',\n cols: 12,\n },\n {\n name: 'details',\n type: 'textarea',\n label: 'Details',\n placeholder: 'Enter Details',\n cols: 12,\n },\n {\n name: 'component',\n type: 'text',\n label: 'Component',\n placeholder: 'Enter Component',\n cols: 8,\n },\n {\n name: 'component_id',\n type: 'text',\n label: 'Component ID',\n placeholder: 'Enter Component ID',\n cols: 4,\n },\n ],\n },\n};\n\n/* =========================\n * Incident\n * ========================= */\nclass Incident extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/incident',\n });\n }\n}\n\nclass IncidentList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Incident,\n endpoint: '/api/incident/incident',\n size: 10,\n ...options,\n });\n }\n}\n\nconst IncidentForms = {\n create: {\n title: 'Create Incident',\n fields: [\n {\n type: 'tabset',\n name: 'settingsTabs',\n tabs: [\n {\n label: 'General',\n fields: [\n {\n name: 'title',\n type: 'text',\n label: 'Title',\n required: true,\n columns: 12\n },\n {\n name: 'details',\n type: 'textarea',\n label: 'Details',\n required: true,\n columns: 12\n },\n\n ]\n },\n {\n label: 'Advanced',\n fields: [\n {\n name: 'priority',\n type: 'select',\n label: 'Priority',\n options: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],\n value: 5,\n columns: 6\n },\n {\n name: 'status',\n type: 'select',\n label: 'Status',\n value: 'open',\n options: ['open', 'investigating', 'resolved', 'closed', \"paused\", \"ignored\"],\n columns: 6\n },\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n value: 'manual',\n columns: 6\n },\n ]\n },\n {\n label: 'Metadata',\n fields: [\n {\n name: 'metadata',\n type: 'json',\n label: 'Metadata',\n value: { \"example\": \"hello world\" },\n rows: 15,\n columns: 12\n }\n ]\n }\n ]\n },\n\n ]\n },\n edit: {\n title: 'Edit Incident',\n fields: [\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n cols: 6,\n },\n {\n name: 'state',\n type: 'select',\n label: 'State',\n placeholder: 'Select State',\n options: ['new', 'opened', 'paused', 'ignore', 'resolved'],\n cols: 3,\n },\n {\n name: 'priority',\n type: 'text',\n label: 'Priority',\n },\n {\n name: 'details',\n type: 'textarea',\n label: 'Description',\n placeholder: 'Enter Name',\n cols: 12,\n },\n {\n name: 'model_name',\n type: 'text',\n label: 'Model',\n placeholder: 'Enter Model',\n cols: 8,\n },\n {\n name: 'model_id',\n type: 'text',\n label: 'Model ID',\n placeholder: 'Enter Model ID',\n cols: 4,\n },\n ],\n },\n};\n\n/* =========================\n * RuleSet / Rule\n * ========================= */\nclass IncidentRuleSet extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset',\n });\n }\n}\n\nclass IncidentRuleSetList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentRuleSet,\n endpoint: '/api/incident/event/ruleset',\n size: 10,\n ...options,\n });\n }\n}\n\nclass IncidentRule extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset/rule',\n });\n }\n}\n\nclass IncidentRuleList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentRule,\n endpoint: '/api/incident/event/ruleset/rule',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * IncidentHistory\n * ========================= */\nclass IncidentHistory extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/incident/history',\n });\n }\n}\n\nclass IncidentHistoryList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: IncidentHistory,\n endpoint: '/api/incident/incident/history',\n size: 10,\n ...options,\n });\n }\n}\n\n\nexport {\n IncidentEvent,\n IncidentEventList,\n IncidentEventForms,\n Incident,\n IncidentList,\n IncidentForms,\n IncidentRuleSet,\n IncidentRuleSetList,\n IncidentRule,\n IncidentRuleList,\n IncidentHistory,\n IncidentHistoryList\n};\n\n/* =========================\n * RuleSet\n * ========================= */\n\n// Bundle By Options\nconst BundleByOptions = [\n { value: 0, label: 'No Bundling' },\n { value: 1, label: 'By Hostname' },\n { value: 2, label: 'By Model Name' },\n { value: 3, label: 'By Model Name + ID' },\n { value: 4, label: 'By Source IP' },\n { value: 5, label: 'By Hostname + Model Name' },\n { value: 6, label: 'By Hostname + Model Name + ID' },\n { value: 7, label: 'By Source IP + Model Name' },\n { value: 8, label: 'By Source IP + Model Name + ID' },\n { value: 9, label: 'By Source IP + Hostname' }\n];\n\n// Match By Options\nconst MatchByOptions = [\n { value: 0, label: 'ALL (must match all rules)' },\n { value: 1, label: 'ANY (match any rule)' }\n];\n\n// Comparator Options\nconst ComparatorOptions = [\n { value: '==', label: 'Equal (==)' },\n { value: 'eq', label: 'Equal (eq)' },\n { value: '>', label: 'Greater Than (>)' },\n { value: '>=', label: 'Greater Than or Equal (>=)' },\n { value: '<', label: 'Less Than (<)' },\n { value: '<=', label: 'Less Than or Equal (<=)' },\n { value: 'contains', label: 'Contains' },\n { value: 'regex', label: 'Regular Expression' }\n];\n\n// Value Type Options\nconst ValueTypeOptions = [\n { value: 'str', label: 'String' },\n { value: 'int', label: 'Integer' },\n { value: 'float', label: 'Float' },\n { value: 'bool', label: 'Boolean' }\n];\n\n// Common Event Fields for ComboInput\nconst CommonEventFields = [\n { value: 'level', label: 'Level', description: 'Event level (error, warning, info, debug)', meta: { type: 'str' } },\n { value: 'source_ip', label: 'Source IP Address', description: 'IP address of the event source', meta: { type: 'str' } },\n { value: 'rule_id', label: 'Rule ID', description: 'Numeric rule identifier', meta: { type: 'int' } },\n { value: 'hostname', label: 'Hostname', description: 'Hostname where event occurred', meta: { type: 'str' } },\n { value: 'component', label: 'Component', description: 'System component name', meta: { type: 'str' } },\n { value: 'component_id', label: 'Component ID', description: 'Component identifier', meta: { type: 'str' } },\n { value: 'category', label: 'Category', description: 'Event category (ossec, auth, api_error, etc.)', meta: { type: 'str' } },\n { value: 'description', label: 'Description', description: 'Event description text', meta: { type: 'str' } },\n { value: 'details', label: 'Details', description: 'Additional event details', meta: { type: 'str' } },\n { value: 'alert_id', label: 'Alert ID', description: 'Numeric alert identifier', meta: { type: 'int' } },\n { value: 'severity', label: 'Severity', description: 'Numeric severity level', meta: { type: 'int' } },\n { value: 'user', label: 'User', description: 'Username associated with event', meta: { type: 'str' } },\n { value: 'action', label: 'Action', description: 'Action that triggered the event', meta: { type: 'str' } },\n { value: 'status', label: 'Status', description: 'Status value or code', meta: { type: 'str' } },\n { value: 'status_code', label: 'Status Code', description: 'Numeric status code (e.g., HTTP status)', meta: { type: 'int' } },\n { value: 'message', label: 'Message', description: 'Event message text', meta: { type: 'str' } },\n { value: 'path', label: 'Path', description: 'File path or URL path', meta: { type: 'str' } },\n { value: 'title', label: 'Title', description: 'OSSEC Title', meta: { type: 'str' } },\n { value: 'country_code', label: 'Country Code', description: 'Country code associated with event', meta: { type: 'str' } },\n { value: 'region', label: 'Region', description: 'Region associated with event', meta: { type: 'str' } },\n { value: 'city', label: 'City', description: 'City associated with event', meta: { type: 'str' } },\n { value: 'http_user_agent', label: 'HTTP User Agent', description: 'User agent string associated with event', meta: { type: 'str' } },\n { value: 'request_path', label: 'Request Path', description: 'Request path associated with event', meta: { type: 'str' } },\n { value: 'method', label: 'Method', description: 'HTTP method or function name', meta: { type: 'str' } }\n];\n\nclass RuleSet extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset',\n });\n }\n}\n\nclass RuleSetList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: RuleSet,\n endpoint: '/api/incident/event/ruleset',\n ...options,\n });\n }\n}\n\nconst RuleSetForms = {\n create: {\n title: 'Create RuleSet',\n size: 'lg',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter ruleset name',\n cols: 12\n },\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n required: true,\n placeholder: 'e.g., ossec, auth, api_error',\n cols: 6\n },\n {\n name: 'priority',\n type: 'number',\n label: 'Priority',\n value: 10,\n required: true,\n cols: 6\n },\n {\n name: 'match_by',\n type: 'select',\n label: 'Match Logic',\n value: 0,\n options: MatchByOptions,\n cols: 12\n },\n {\n name: 'bundle_by',\n type: 'select',\n label: 'Bundle By',\n value: 0,\n options: BundleByOptions,\n cols: 12\n },\n {\n name: 'bundle_minutes',\n type: 'number',\n label: 'Bundle Minutes',\n value: 10,\n placeholder: 'Time window for bundling',\n cols: 12\n },\n {\n name: 'handler',\n type: 'text',\n label: 'Handler',\n placeholder: 'e.g., email://user@company.com, task://name',\n cols: 12\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n value: true,\n cols: 6\n },\n {\n name: 'bundle_by_rule_set',\n type: 'switch',\n label: 'Bundle by Rule Set',\n value: true,\n cols: 6\n }\n ]\n },\n edit: {\n title: 'Edit RuleSet',\n size: 'lg',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter ruleset name',\n cols: 12\n },\n {\n name: 'category',\n type: 'text',\n label: 'Category',\n required: true,\n placeholder: 'e.g., ossec, auth, api_error',\n cols: 6\n },\n {\n name: 'priority',\n type: 'number',\n label: 'Priority',\n required: true,\n cols: 6\n },\n {\n name: 'match_by',\n type: 'select',\n label: 'Match Logic',\n options: MatchByOptions,\n cols: 12\n },\n {\n name: 'bundle_by',\n type: 'select',\n label: 'Bundle By',\n options: BundleByOptions,\n cols: 12\n },\n {\n name: 'bundle_minutes',\n type: 'select',\n label: 'Bundle Minutes',\n placeholder: 'Time window for bundling',\n \"options\": [\n {\"value\": 0, \"label\": \"Disabled - don't bundle by time\"},\n {\"value\": 5, \"label\": \"5 minutes\"},\n {\"value\": 10, \"label\": \"10 minutes\"},\n {\"value\": 15, \"label\": \"15 minutes\"},\n {\"value\": 30, \"label\": \"30 minutes\"},\n {\"value\": 60, \"label\": \"1 hour\"},\n {\"value\": 120, \"label\": \"2 hours\"},\n {\"value\": 360, \"label\": \"6 hours\"},\n {\"value\": 720, \"label\": \"12 hours\"},\n {\"value\": 1440, \"label\": \"1 day\"},\n {\"value\": null, \"label\": \"No limit - bundle forever\"}\n ],\n cols: 12\n },\n {\n name: 'handler',\n type: 'text',\n label: 'Handler',\n placeholder: 'e.g., email://user@company.com, task://name',\n cols: 12\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n value: true,\n cols: 6\n },\n {\n name: 'bundle_by_rule_set',\n type: 'switch',\n label: 'Bundle by Rule Set',\n value: true,\n cols: 6\n }\n ]\n }\n};\n\n/* =========================\n * Rule\n * ========================= */\nclass Rule extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/event/ruleset/rule',\n });\n }\n}\n\nclass RuleList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Rule,\n endpoint: '/api/incident/event/ruleset/rule',\n ...options,\n });\n }\n}\n\nconst RuleForms = {\n create: {\n title: 'Create Rule',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter rule name',\n cols: 12\n },\n {\n name: 'field_name',\n type: 'combo',\n label: 'Field Name',\n required: true,\n placeholder: 'Select or enter field name...',\n options: CommonEventFields,\n allowCustom: true,\n showDescription: true,\n help: 'Select a common field or type a custom field name',\n cols: 12\n },\n {\n name: 'comparator',\n type: 'select',\n label: 'Comparator',\n required: true,\n options: ComparatorOptions,\n cols: 6\n },\n {\n name: 'value_type',\n type: 'select',\n label: 'Value Type',\n required: true,\n options: ValueTypeOptions,\n value: 'str',\n cols: 6\n },\n {\n name: 'value',\n type: 'text',\n label: 'Value',\n required: true,\n placeholder: 'Enter comparison value',\n cols: 12\n }\n ]\n },\n edit: {\n title: 'Edit Rule',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Name',\n required: true,\n placeholder: 'Enter rule name',\n cols: 12\n },\n {\n name: 'field_name',\n type: 'combo',\n label: 'Field Name',\n required: true,\n placeholder: 'Select or enter field name...',\n options: CommonEventFields,\n allowCustom: true,\n showDescription: true,\n help: 'Select a common field or type a custom field name',\n cols: 12\n },\n {\n name: 'comparator',\n type: 'select',\n label: 'Comparator',\n required: true,\n options: ComparatorOptions,\n cols: 6\n },\n {\n name: 'value_type',\n type: 'select',\n label: 'Value Type',\n required: true,\n options: ValueTypeOptions,\n cols: 6\n },\n {\n name: 'value',\n type: 'text',\n label: 'Value',\n required: true,\n placeholder: 'Enter comparison value',\n cols: 12\n }\n ]\n }\n};\n\n// Attach forms to models\nRuleSet.EDIT_FORM = RuleSetForms.edit;\nRuleSet.ADD_FORM = RuleSetForms.create;\nRuleSet.CREATE_FORM = RuleSetForms.create; // Alias for compatibility\nRuleSet.BundleByOptions = BundleByOptions;\nRuleSet.MatchByOptions = MatchByOptions;\n\nRule.EDIT_FORM = RuleForms.edit;\nRule.ADD_FORM = RuleForms.create;\nRule.CREATE_FORM = RuleForms.create; // Alias for compatibility\nRule.ComparatorOptions = ComparatorOptions;\nRule.ValueTypeOptions = ValueTypeOptions;\n\nexport {\n RuleSet,\n RuleSetList,\n RuleSetForms,\n Rule,\n RuleList,\n RuleForms,\n IncidentStats,\n BundleByOptions,\n MatchByOptions,\n ComparatorOptions,\n ValueTypeOptions,\n CommonEventFields\n};\n","/**\n * Job Model - Async Job Engine management\n * Supports object-oriented POST_SAVE_ACTIONS pattern from Jobs API\n */\n\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Job extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/job'\n });\n }\n\n // Object-oriented actions using POST_SAVE_ACTIONS pattern\n async cancel() {\n const response = await this.save({ cancel_request: true });\n if (response.success && response.data.status) {\n // Update local status if successful\n this.set('cancel_requested', true);\n }\n return response;\n }\n\n async retry(delay = null) {\n const data = delay ?\n { retry_request: { retry: true, delay } } :\n { retry_request: true };\n\n const response = await this.save(data);\n if (response.success && response.data.status && response.data.new_job_id) {\n // Return new job ID if created\n return {\n ...response,\n newJobId: response.data.new_job_id,\n originalJobId: response.data.original_job_id\n };\n }\n return response;\n }\n\n async getDetailedStatus() {\n const response = await this.save({ get_status: true });\n if (response.success && response.data.status) {\n // Update local data with detailed status\n this.set(response.data.data);\n }\n return response;\n }\n\n async cloneJob(newPayload = {}) {\n const publishData = {\n publish_job: {\n payload: newPayload,\n ...newPayload\n }\n };\n\n const response = await this.save(publishData);\n if (response.success && response.data.status && response.data.job_id) {\n return {\n ...response,\n newJobId: response.data.job_id,\n templateJobId: response.data.template_job_id\n };\n }\n return response;\n }\n\n // Status helper methods\n isActive() {\n const status = this.get('status');\n return ['pending', 'running'].includes(status);\n }\n\n isTerminal() {\n const status = this.get('status');\n return ['completed', 'failed', 'canceled', 'expired'].includes(status);\n }\n\n canRetry() {\n const status = this.get('status');\n return ['failed', 'canceled', 'expired'].includes(status) && this.get('is_retriable') !== false;\n }\n\n canCancel() {\n const status = this.get('status');\n return ['pending', 'running'].includes(status) && !this.get('cancel_requested');\n }\n\n // Get status badge class for UI\n getStatusBadgeClass() {\n const status = this.get('status');\n const classes = {\n 'pending': 'bg-primary',\n 'running': 'bg-success',\n 'completed': 'bg-info',\n 'failed': 'bg-danger',\n 'canceled': 'bg-secondary',\n 'expired': 'bg-warning'\n };\n return classes[status] || 'bg-secondary';\n }\n\n // Get status icon\n getStatusIcon() {\n const status = this.get('status');\n const icons = {\n 'pending': 'bi-hourglass',\n 'running': 'bi-arrow-repeat',\n 'completed': 'bi-check-circle',\n 'failed': 'bi-x-octagon',\n 'canceled': 'bi-x-circle',\n 'expired': 'bi-clock'\n };\n return icons[status] || 'bi-question-circle';\n }\n\n // Get recent events for timeline\n getEvents() {\n return this.get('recent_events') || [];\n }\n\n // Get formatted duration\n getFormattedDuration() {\n const duration = this.get('duration_ms');\n if (!duration || duration === 0) return 'N/A';\n\n if (duration < 1000) return `${duration}ms`;\n if (duration < 60000) return `${(duration / 1000).toFixed(1)}s`;\n if (duration < 3600000) return `${(duration / 60000).toFixed(1)}m`;\n return `${(duration / 3600000).toFixed(1)}h`;\n }\n\n // Get queue position if available\n getQueuePosition() {\n return this.get('queue_position');\n }\n\n // Check if job has expired\n hasExpired() {\n const expiresAt = this.get('expires_at');\n if (!expiresAt) return false;\n return new Date(expiresAt) < new Date();\n }\n\n // Get runner information\n getRunnerId() {\n return this.get('runner_id');\n }\n\n // Get payload data\n getPayload() {\n return this.get('payload') || {};\n }\n\n // Get metadata\n getMetadata() {\n return this.get('metadata') || {};\n }\n}\n\nclass JobList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Job,\n endpoint: '/api/jobs/job',\n ...options\n });\n }\n\n // Filter by status\n async fetchByStatus(status, params = {}) {\n return this.fetch({ status, ...params });\n }\n\n // Filter by channel\n async fetchByChannel(channel, params = {}) {\n return this.fetch({ channel, ...params });\n }\n\n // Get pending jobs\n async fetchPending(params = {}) {\n return this.fetchByStatus('pending', params);\n }\n\n // Get running jobs\n async fetchRunning(params = {}) {\n return this.fetchByStatus('running', params);\n }\n\n // Get completed jobs\n async fetchCompleted(params = {}) {\n return this.fetchByStatus('completed', params);\n }\n\n // Get failed jobs\n async fetchFailed(params = {}) {\n return this.fetchByStatus('failed', params);\n }\n\n // Get scheduled jobs (those with run_at)\n async fetchScheduled(params = {}) {\n return this.fetch({\n scheduled: true, // This may need to be adjusted based on actual API\n ...params\n });\n }\n}\n\n// Form configurations for job management\nconst JobForms = {\n publish: {\n title: 'Publish New Job',\n fields: [\n {\n name: 'func',\n type: 'text',\n label: 'Function',\n required: true,\n placeholder: 'myapp.jobs.send_email',\n help: 'Module path to job function'\n },\n {\n name: 'channel',\n type: 'text',\n label: 'Channel',\n value: 'default',\n help: 'Queue channel (default: \"default\")'\n },\n {\n name: 'payload',\n type: 'textarea',\n label: 'Payload (JSON)',\n required: true,\n rows: 8,\n placeholder: '{\\n \"key\": \"value\"\\n}',\n help: 'JSON data passed to the job function'\n },\n {\n name: 'delay',\n type: 'number',\n label: 'Delay (seconds)',\n min: 0,\n help: 'Delay execution by specified seconds'\n },\n {\n name: 'run_at',\n type: 'datetime-local',\n label: 'Run At',\n help: 'Schedule for specific date/time'\n },\n {\n name: 'max_retries',\n type: 'number',\n label: 'Max Retries',\n value: 3,\n min: 0,\n max: 10\n },\n {\n name: 'expires_in',\n type: 'number',\n label: 'Expires In (seconds)',\n value: 900,\n min: 60,\n help: 'Job will expire if not completed in this time'\n },\n {\n name: 'broadcast',\n type: 'switch',\n label: 'Broadcast to All Workers',\n help: 'Execute on all available workers'\n }\n ]\n },\n\n retry: {\n title: 'Retry Job',\n fields: [\n {\n name: 'delay',\n type: 'number',\n label: 'Delay (seconds)',\n value: 0,\n min: 0,\n help: 'Delay before retry (0 = immediate)'\n }\n ]\n },\n\n clone: {\n title: 'Clone Job',\n fields: [\n {\n name: 'channel',\n type: 'text',\n label: 'Channel',\n help: 'Override channel for cloned job'\n },\n {\n name: 'payload',\n type: 'textarea',\n label: 'Modified Payload (JSON)',\n rows: 8,\n help: 'Modified payload for cloned job'\n },\n {\n name: 'delay',\n type: 'number',\n label: 'Delay (seconds)',\n min: 0\n }\n ]\n }\n};\n\n// Static method to publish new job directly\nJob.publish = async function(jobData) {\n return await rest.POST('/api/jobs/publish', jobData);\n};\n\n// Static method to get system stats\nJob.getStats = async function() {\n const response = await rest.GET('/api/jobs/stats');\n return response.success ? response.data : null;\n};\n\n// Static method to get system health\nJob.getHealth = async function(channel = null) {\n const endpoint = channel ? `/api/jobs/health/${channel}` : '/api/jobs/health';\n const response = rest.GET(endpoint);\n return response.success ? response.data : null;\n};\n\n// Static method to run test job\nJob.test = async function() {\n return await rest.POST('/api/jobs/test', {});\n};\n\n// Static method to run test jobs\nJob.tests = async function() {\n return await rest.POST('/api/jobs/tests', {});\n};\n\nJob.clearStuck = async function(channel = null) {\n const payload = channel ? { channel } : {};\n return await rest.POST('/api/jobs/control/clear-stuck', payload);\n};\n\nJob.clearChannel = async function(channel) {\n return await rest.POST('/api/jobs/control/clear-queue', { channel, confirm:\"yes\" });\n};\n\nJob.cleanConsumers = async function() {\n return await rest.POST('/api/jobs/control/cleanup-consumers');\n};\n\nJob.purgeJobs = async function(days_old) {\n return await rest.POST('/api/jobs/control/purge', { days_old });\n};\n\n\nclass JobLog extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/logs'\n });\n }\n}\n\nclass JobLogList extends Collection {\n constructor(options = {}) {\n super({\n endpoint: '/api/jobs/logs',\n model: JobLog,\n ...options\n });\n }\n}\n\n\nclass JobEvent extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/event'\n });\n }\n}\n\nclass JobEventList extends Collection {\n constructor(options = {}) {\n super({\n endpoint: '/api/jobs/event',\n model: JobEvent,\n ...options\n });\n }\n}\n\nclass JobsEngineStats extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/jobs/stats',\n requiresId: false\n });\n }\n}\n\nexport {\n Job, JobList, JobForms,\n JobLog, JobLogList, JobEvent,\n JobEventList, JobsEngineStats };\n","/**\n * JobRunner Model - Job Engine Runner/Worker management\n * Manages worker processes that execute jobs\n */\n\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\nclass JobRunner extends Model {\n constructor(data = {}) {\n // Map runner_id to id for proper Model handling\n if (data.runner_id && !data.id) {\n data.id = data.runner_id;\n }\n \n super(data, {\n endpoint: '/api/jobs/runners',\n idAttribute: 'runner_id' // Tell the model to use runner_id as the ID field\n });\n }\n\n // Ping this runner to test connectivity\n async ping(timeout = 2.0) {\n const app = this.getApp();\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n\n const response = await app.rest.POST('/api/jobs/runners/ping', {\n runner_id: this.get('runner_id'),\n timeout: timeout\n });\n\n if (response.success && response.data.status) {\n // Update ping status\n this.set('last_heartbeat', new Date().toISOString());\n this.set('ping_status', response.data.ping_status || 'success');\n }\n\n return response;\n }\n\n // Shutdown this runner gracefully or forcefully\n async shutdown(graceful = true) {\n const app = this.getApp();\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n\n const response = await app.rest.POST('/api/jobs/runners/shutdown', {\n runner_id: this.get('runner_id'),\n graceful: graceful\n });\n\n if (response.success && response.data.status) {\n // Update status to shutting down (alive becomes false)\n this.set('alive', false);\n }\n\n return response;\n }\n\n // Get channels handled by this runner\n getChannels() {\n return this.get('channels') || [];\n }\n\n // Get runner status with appropriate styling based on alive field\n getStatusBadgeClass() {\n const alive = this.get('alive');\n return alive ? 'bg-success' : 'bg-danger';\n }\n\n // Get status icon based on alive field\n getStatusIcon() {\n const alive = this.get('alive');\n return alive ? 'bi-check-circle-fill' : 'bi-x-octagon-fill';\n }\n\n // Check if runner is active (alive)\n isActive() {\n return this.get('alive') === true;\n }\n\n // Check if runner is healthy (active and recent heartbeat)\n isHealthy() {\n if (!this.isActive()) return false;\n \n const lastHeartbeat = this.get('last_heartbeat');\n if (!lastHeartbeat) return false;\n \n // Consider healthy if heartbeat within last 2 minutes\n const heartbeatAge = (Date.now() - new Date(lastHeartbeat).getTime()) / 1000;\n return heartbeatAge < 120;\n }\n\n // Get formatted heartbeat age\n getFormattedHeartbeatAge() {\n const lastHeartbeat = this.get('last_heartbeat');\n if (!lastHeartbeat) return 'Never';\n \n const heartbeatAge = (Date.now() - new Date(lastHeartbeat).getTime()) / 1000;\n \n if (heartbeatAge < 60) return `${Math.round(heartbeatAge)}s ago`;\n if (heartbeatAge < 3600) return `${Math.round(heartbeatAge / 60)}m ago`;\n return `${Math.round(heartbeatAge / 3600)}h ago`;\n }\n\n // Get current utilization (from jobs processed vs capacity)\n getUtilization() {\n const processed = this.get('jobs_processed') || 0;\n const failed = this.get('jobs_failed') || 0;\n const total = processed + failed;\n // Since we don't have max capacity from API, return based on recent activity\n return total > 10 ? 100 : Math.min(total * 10, 100);\n }\n\n // Get formatted uptime based on started timestamp\n getFormattedUptime() {\n const started = this.get('started');\n if (!started) return 'Unknown';\n \n const startTime = new Date(started);\n const now = new Date();\n const diffMs = now - startTime;\n const diffSeconds = Math.floor(diffMs / 1000);\n \n if (diffSeconds < 60) return `${diffSeconds}s`;\n if (diffSeconds < 3600) return `${Math.floor(diffSeconds / 60)}m`;\n if (diffSeconds < 86400) return `${Math.floor(diffSeconds / 3600)}h`;\n return `${Math.floor(diffSeconds / 86400)}d`;\n }\n\n // Get worker capacity info\n getWorkerInfo() {\n const processed = this.get('jobs_processed') || 0;\n const failed = this.get('jobs_failed') || 0;\n return {\n processed,\n failed,\n total: processed + failed,\n alive: this.get('alive'),\n utilization: this.getUtilization()\n };\n }\n\n // Get display name for runner\n getDisplayName() {\n const runnerId = this.get('runner_id');\n if (!runnerId) return 'Unknown Runner';\n \n // Extract hostname-like portion if it exists\n const parts = runnerId.split('-');\n return parts.length > 1 ? parts[0] : runnerId;\n }\n\n // Check if runner can be controlled\n canControl() {\n return this.get('alive') === true;\n }\n}\n\nclass JobRunnerList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: JobRunner,\n endpoint: '/api/jobs/runners',\n ...options\n });\n }\n\n // Get only active runners\n getActive() {\n return this.where(runner => runner.isActive());\n }\n\n // Get runners by channel\n getByChannel(channel) {\n return this.where(runner => runner.getChannels().includes(channel));\n }\n\n // Get healthy runners\n getHealthy() {\n return this.where(runner => runner.isHealthy());\n }\n\n // Get total jobs processed across all runners\n getTotalProcessed() {\n return this.models.reduce((total, runner) => {\n return total + (runner.get('jobs_processed') || 0);\n }, 0);\n }\n\n // Get total jobs failed across all runners\n getTotalFailed() {\n return this.models.reduce((total, runner) => {\n return total + (runner.get('jobs_failed') || 0);\n }, 0);\n }\n\n // Get overall system health percentage (alive runners / total runners)\n getSystemHealth() {\n if (this.models.length === 0) return 0;\n const aliveRunners = this.models.filter(runner => runner.get('alive')).length;\n return Math.round((aliveRunners / this.models.length) * 100);\n }\n\n // Get unique channels across all runners\n getAllChannels() {\n const channels = new Set();\n this.models.forEach(runner => {\n runner.getChannels().forEach(channel => channels.add(channel));\n });\n return Array.from(channels).sort();\n }\n}\n\n// Static methods for system-wide runner management\nJobRunner.ping = async function(runnerId, timeout = 2.0) {\n const app = typeof window !== 'undefined' && window.app;\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n \n return await app.rest.POST('/api/jobs/runners/ping', {\n runner_id: runnerId,\n timeout\n });\n};\n\nJobRunner.shutdown = async function(runnerId, graceful = true) {\n const app = typeof window !== 'undefined' && window.app;\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n \n return await app.rest.POST('/api/jobs/runners/shutdown', {\n runner_id: runnerId,\n graceful\n });\n};\n\nJobRunner.broadcast = async function(command, data = {}, timeout = 2.0) {\n const app = typeof window !== 'undefined' && window.app;\n if (!app || !app.rest) {\n throw new Error('App or REST client not available');\n }\n \n return await app.rest.POST('/api/jobs/runners/broadcast', {\n command,\n data,\n timeout\n });\n};\n\n// Broadcast commands\nJobRunner.broadcastStatus = async function(timeout = 2.0) {\n return JobRunner.broadcast('status', {}, timeout);\n};\n\nJobRunner.broadcastShutdown = async function(timeout = 2.0) {\n return JobRunner.broadcast('shutdown', {}, timeout);\n};\n\nJobRunner.broadcastPause = async function(timeout = 2.0) {\n return JobRunner.broadcast('pause', {}, timeout);\n};\n\nJobRunner.broadcastResume = async function(timeout = 2.0) {\n return JobRunner.broadcast('resume', {}, timeout);\n};\n\nJobRunner.broadcastReload = async function(timeout = 2.0) {\n return JobRunner.broadcast('reload', {}, timeout);\n};\n\nconst JobRunnerForms = {\n broadcast: {\n title: 'Broadcast Command',\n fields: [\n {\n name: 'command',\n type: 'select',\n label: 'Command',\n required: true,\n options: [\n { value: 'status', label: 'Status Check' },\n { value: 'pause', label: 'Pause Processing' },\n { value: 'resume', label: 'Resume Processing' },\n { value: 'reload', label: 'Reload Configuration' },\n { value: 'shutdown', label: 'Shutdown All Runners' }\n ],\n help: 'Command to send to all runners'\n },\n {\n name: 'timeout',\n type: 'number',\n label: 'Timeout (seconds)',\n value: 2.0,\n min: 0.5,\n max: 10.0,\n step: 0.5,\n help: 'How long to wait for responses'\n }\n ]\n }\n};\n\nexport { JobRunner, JobRunnerList, JobRunnerForms };","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Log extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/logs',\n });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass LogList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Log,\n endpoint: '/api/logs',\n size: 10,\n ...options,\n });\n }\n}\n\nexport { Log, LogList };\n","\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * Model\n * ========================= */\nclass Member extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group/member',\n });\n }\n\n hasPermission(permission) {\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n return permissions[permission] == true;\n }\n\n async fetchForGroup(groupId) {\n return await this.fetch({ url: `/api/group/${groupId}/member` });\n }\n}\n\n/* =========================\n * Collection\n * ========================= */\nclass MemberList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Member,\n endpoint: '/api/group/member',\n size: 10,\n ...options,\n });\n }\n}\n\n/* =========================\n * Forms\n * ========================= */\nconst MemberForms = {\n\n edit: {\n title: 'Edit Membership',\n fields: [\n {\n name: 'user.display_name',\n type: 'text',\n label: 'Display Name',\n placeholder: 'Enter Display Name'\n },\n {\n name: 'metadata.role',\n type: 'text',\n label: 'Role',\n placeholder: 'Enter role'\n },\n {\n name: `is_active`,\n type: 'switch',\n label: \"Is Enabled\",\n columns: 12\n }\n ]\n }\n};\n\n\nMember.EDIT_FORM = MemberForms.edit;\nMember.ADD_FORM = MemberForms.create;\n\n\nexport { Member, MemberList, MemberForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\nclass MetricsPermission extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/metrics/permissions',\n id_key: 'account'\n });\n }\n}\n\nclass MetricsPermissionList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: MetricsPermission,\n endpoint: '/api/metrics/permissions',\n ...options,\n });\n }\n\n}\n\nconst MetricsForms = {\n edit: {\n title: 'Edit Metrics Permissions',\n fields: [\n { name: 'account', type: 'text', label: 'Account', columns:12 },\n { name: 'view_permissions', type: 'tags', label: 'View Permissions', help: 'Enter permissions or \"public\"', columns:12 },\n { name: 'write_permissions', type: 'tags', label: 'Write Permissions', help: 'Enter permissions', columns:12 },\n ]\n }\n};\n\nexport { MetricsPermission, MetricsPermissionList, MetricsForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport { GroupList } from './Group.js';\n\n// =========================\n// PushDevice\n// =========================\nclass PushDevice extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push' });\n }\n}\n\nclass PushDeviceList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushDevice, endpoint: '/api/account/devices/push', ...options });\n }\n}\n\n// =========================\n// PushTemplate\n// =========================\nclass PushTemplate extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push/templates' });\n }\n}\n\nclass PushTemplateList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushTemplate, endpoint: '/api/account/devices/push/templates', ...options });\n }\n}\n\n// =========================\n// PushConfig\n// =========================\nclass PushConfig extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push/config' });\n }\n}\n\nclass PushConfigList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushConfig, endpoint: '/api/account/devices/push/config', ...options });\n }\n}\n\n// =========================\n// PushDelivery\n// =========================\nclass PushDelivery extends Model {\n constructor(data = {}) {\n super(data, { endpoint: '/api/account/devices/push/deliveries' });\n }\n}\n\nclass PushDeliveryList extends Collection {\n constructor(options = {}) {\n super({ ModelClass: PushDelivery, endpoint: '/api/account/devices/push/deliveries', ...options });\n }\n}\n\n// =========================\n// Forms\n// =========================\nconst PushConfigForms = {\n create: {\n title: 'Create Push Configuration',\n fields: [\n { name: 'name', label: 'Name', required: true },\n { type: 'collection', name: 'group', label: 'Group (optional)', Collection: GroupList, labelField: 'name', valueField: 'id' },\n { name: 'fcm_service_account', label: 'Service Account', type: \"textarea\", rows: 10},\n ]\n },\n edit: {\n title: 'Edit Push Configuration',\n fields: [\n { name: 'name', label: 'Name', required: true },\n { type: 'collection', name: 'group', label: 'Group (optional)', Collection: GroupList, labelField: 'name', valueField: 'id' },\n { name: 'fcm_service_account', label: 'Service Account', type: \"textarea\", rows: 10},\n { name: 'is_active', label: 'Is Active', type: 'switch', value: true },\n ]\n }\n};\n\nconst PushTemplateForms = {\n edit: {\n title: 'Edit Push Template',\n fields: [\n { name: 'name', label: 'Name', required: true },\n { name: 'category', label: 'Category', required: true },\n { \n type: 'collection', \n name: 'group', \n label: 'Group (optional)', \n Collection: GroupList, \n labelField: 'name', \n valueField: 'id',\n defaultParams: { is_active: true } // Example: filter to active groups only\n },\n { name: 'title_template', label: 'Title Template', required: true },\n { name: 'body_template', label: 'Body Template', type: 'textarea', required: true },\n { name: 'action_url', label: 'Action URL' },\n { name: 'priority', label: 'Priority', type: 'select', options: ['high', 'normal'] },\n { name: 'variables', label: 'Variables', type: 'json', help: 'JSON format' },\n { name: 'is_active', label: 'Is Active', type: 'switch' },\n ]\n }\n};\n\nPushConfigForms.create = PushConfigForms.edit;\nPushTemplateForms.create = PushTemplateForms.edit;\n\n\nexport {\n PushDevice, PushDeviceList,\n PushTemplate, PushTemplateList,\n PushConfig, PushConfigList,\n PushDelivery, PushDeliveryList,\n PushConfigForms, PushTemplateForms\n};\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/* =========================\n * GeoLocatedIP\n * ========================= */\n\nconst GeoIPForms = {\n editLocation: {\n title: 'Edit Location',\n size: 'lg',\n fields: [\n { name: 'ip_address', label: 'IP Address', type: 'text', required: true, readonly: true, cols: 6 },\n { name: 'subnet', label: 'Subnet', type: 'text', cols: 6 },\n { name: 'country_name', label: 'Country', type: 'text', cols: 6 },\n { name: 'country_code', label: 'Country Code', type: 'text', cols: 6 },\n { name: 'region', label: 'Region', type: 'text', cols: 6 },\n { name: 'city', label: 'City', type: 'text', cols: 6 },\n { name: 'postal_code', label: 'Postal Code', type: 'text', cols: 6 },\n { name: 'timezone', label: 'Timezone', type: 'text', cols: 6 },\n { name: 'latitude', label: 'Latitude', type: 'number', step: 'any', cols: 6 },\n { name: 'longitude', label: 'Longitude', type: 'number', step: 'any', cols: 6 },\n ]\n },\n editSecurity: {\n title: 'Edit Security',\n size: 'md',\n fields: [\n { \n name: 'threat_level', \n label: 'Threat Level', \n type: 'select', \n cols: 12,\n options: [\n { value: '', label: 'None' },\n { value: 'low', label: 'Low' },\n { value: 'medium', label: 'Medium' },\n { value: 'high', label: 'High' },\n { value: 'critical', label: 'Critical' }\n ]\n },\n { name: 'is_threat', label: 'Threat', type: 'switch', cols: 6 },\n { name: 'is_suspicious', label: 'Suspicious', type: 'switch', cols: 6 },\n { name: 'is_known_attacker', label: 'Known Attacker', type: 'switch', cols: 6 },\n { name: 'is_known_abuser', label: 'Known Abuser', type: 'switch', cols: 6 },\n { name: 'risk_score', label: 'Risk Score', type: 'number', cols: 6 },\n { name: 'is_tor', label: 'TOR Exit Node', type: 'switch', cols: 6 },\n { name: 'is_vpn', label: 'VPN', type: 'switch', cols: 6 },\n { name: 'is_proxy', label: 'Proxy', type: 'switch', cols: 6 },\n { name: 'is_cloud', label: 'Cloud Provider', type: 'switch', cols: 6 },\n { name: 'is_datacenter', label: 'Datacenter', type: 'switch', cols: 6 }\n ]\n },\n editNetwork: {\n title: 'Edit Network',\n size: 'md',\n fields: [\n { name: 'asn', label: 'ASN', type: 'text', cols: 6 },\n { name: 'asn_org', label: 'ASN Organization', type: 'text', cols: 6 },\n { name: 'isp', label: 'ISP', type: 'text', cols: 12 },\n { name: 'connection_type', label: 'Connection Type', type: 'text', cols: 6 },\n { name: 'provider', label: 'Provider', type: 'text', cols: 6 },\n { name: 'is_mobile', label: 'Mobile Connection', type: 'switch', cols: 6 },\n { name: 'mobile_carrier', label: 'Mobile Carrier', type: 'text', cols: 6 },\n { name: 'last_seen', label: 'Last Seen', type: 'datetime', cols: 12 }\n ]\n }\n};\n\nclass GeoLocatedIP extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/system/geoip',\n });\n }\n\n static async lookup(ip) {\n const model = new GeoLocatedIP();\n const resp = await model.rest.GET('/api/system/geoip/lookup', { ip });\n if (resp.success && resp.data && resp.data.data) {\n return new GeoLocatedIP(resp.data.data);\n }\n return null;\n }\n}\n\n// Attach forms to model (use Location as default EDIT_FORM for TableView)\nGeoLocatedIP.EDIT_FORM = GeoIPForms.editLocation;\nGeoLocatedIP.EDIT_LOCATION_FORM = GeoIPForms.editLocation;\nGeoLocatedIP.EDIT_SECURITY_FORM = GeoIPForms.editSecurity;\nGeoLocatedIP.EDIT_NETWORK_FORM = GeoIPForms.editNetwork;\n\nclass GeoLocatedIPList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: GeoLocatedIP,\n endpoint: '/api/system/geoip',\n ...options,\n });\n }\n}\n\nexport { GeoLocatedIP, GeoLocatedIPList };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\nimport { UserList } from './User.js';\nimport { IncidentList } from './Incident.js';\n\n/* =========================\n * Ticket\n * ========================= */\nclass Ticket extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/ticket',\n });\n }\n}\n\nclass TicketList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Ticket,\n endpoint: '/api/incident/ticket',\n ...options,\n });\n }\n}\n\n\nconst TicketCategories = {\n 'ticket': 'Ticket',\n 'bug': 'Bug',\n 'feature': 'Feature Request',\n 'incident': 'Incident',\n 'security': 'Security Incident',\n 'fulfillment': 'Fulfillment',\n 'new_user': 'New User',\n 'new_group': 'New Group',\n 'qa': 'Quality Assurance'\n};\n\n// Convert TicketCategories to select options\nconst TicketCategoriesOptions = Object.entries(TicketCategories).map(([key, label]) => ({\n value: key,\n label: label\n}));\n\n\nconst TicketForms = {\n create: {\n title: 'Create Ticket',\n fields: [\n {\n name: 'title', type: 'text',\n label: 'Title', required: true, cols: 12\n },\n { name: 'description', type: 'textarea', label: 'Description', required: false, cols: 12 },\n {\n name: \"category\", type: \"select\", label: \"Category\",\n options: TicketCategoriesOptions, cols: 12, value: 'ticket' },\n {\n name: 'priority', type: 'number',\n label: 'Priority', value: 5, cols: 6\n },\n {\n name: 'status', type: 'select', label: \"Status\",\n options: [\"new\", \"open\", \"paused\", \"resolved\", \"qa\", \"ignored\"],\n cols: 6, value: 'new'\n },\n {\n type: 'collection', name: 'assignee',\n label: 'Assignee', Collection: UserList,\n labelField: 'display_name',\n valueField: 'id',\n cols: 12\n },\n {\n type: 'collection', name: 'incident',\n label: 'Incident', Collection: IncidentList,\n labelField: 'title',\n valueField: 'id',\n cols: 12\n },\n ]\n },\n edit: {\n title: 'Edit Ticket',\n fields: [\n { name: 'title', type: 'text', label: 'Title', required: true, cols: 12 },\n { name: 'description', type: 'textarea', label: 'Description', required: false, cols: 12 },\n { name: \"category\", type: \"select\", label: \"Category\", options: TicketCategoriesOptions, cols: 12 },\n { name: 'priority', type: 'number', label: 'Priority', cols: 6 },\n { name: 'status', type: 'select', label: 'Status', options: [\"new\", \"open\", \"paused\", \"resolved\", \"qa\", \"ignored\"], cols: 6 },\n { type: 'collection', name: 'assignee', label: 'Assignee', Collection: UserList, labelField: 'display_name', valueField: 'id', cols: 12 },\n { type: 'collection', name: 'incident', label: 'Incident', Collection: IncidentList, labelField: 'title', valueField: 'id', cols: 12 },\n ]\n }\n};\n\n/* =========================\n * TicketNote\n * ========================= */\nclass TicketNote extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/incident/ticket/note',\n });\n }\n}\n\nclass TicketNoteList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: TicketNote,\n endpoint: '/api/incident/ticket/note',\n ...options,\n });\n }\n}\n\nexport { Ticket, TicketList, TicketNote, TicketNoteList, TicketForms, TicketCategories };\n","/**\n * TableRow - Individual row view for TableView\n *\n * Extends ListViewItem to render table rows with proper cell formatting\n * and support for all table features like selection, actions, and context menus.\n *\n * @example\n * const row = new TableRow({\n * model: userModel,\n * columns: tableColumns,\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport ListViewItem from '../list/ListViewItem.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\n\nclass TableRow extends ListViewItem {\n constructor(options = {}) {\n super({\n tagName: 'tr',\n className: 'table-row',\n enableTooltips: true,\n ...options\n });\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.tableView = options.tableView || options.listView || null;\n\n // Inline editing state\n this.editingCells = new Set(); // Track which cells are being edited\n\n // Override template to generate table cells\n this.template = this.buildRowTemplate();\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Build the row template with table cells\n */\n buildRowTemplate() {\n let template = '';\n\n // Selection checkbox cell\n if (this.tableView && this.tableView.isSelectable()) {\n template += `\n <td style=\"padding: 0;\">\n <div class=\"mojo-select-cell {{#selected}}selected{{/selected}}\"\n data-action=\"select\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </td>\n `;\n }\n\n // Data cells for each column\n this.columns.forEach(column => {\n const cellClass = column.class || column.className || '';\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n const editableClass = column.editable ? 'editable-cell' : '';\n const combinedClasses = [cellClass, responsiveClasses, editableClass].filter(c => c).join(' ');\n const cellContent = this.buildCellTemplate(column);\n\n // Determine cell action\n let cellAction = column.action;\n if (!cellAction && column.editable) {\n cellAction = 'edit-cell';\n } else if (!cellAction && this.tableView.rowAction) {\n cellAction = this.tableView.rowAction;\n }\n\n if (cellAction) {\n template += `<td class=\"${combinedClasses}\" data-action=\"${cellAction}\" data-column=\"${column.key}\">${cellContent}</td>`;\n } else {\n template += `<td class=\"${combinedClasses}\" data-column=\"${column.key}\">${cellContent}</td>`;\n }\n });\n\n // Actions cell\n if (this.actions) {\n template += this.buildActionsTemplate();\n } else if (this.contextMenu) {\n template += this.buildContextMenuTemplate();\n }\n\n return template;\n }\n\n /**\n * Build template for a single cell\n */\n /**\n * Build template for a single cell\n */\n buildCellTemplate(column) {\n // Build path for Mustache to access the value\n const path = `model.${column.key}`;\n // Support both 'formatter' and 'format' for consistency with DataView\n const formatter = column.formatter || column.format;\n if (formatter) {\n // For string formatters that are pipe expressions\n if (typeof formatter === 'string') {\n return `{{{${path}|${formatter}}}}`;\n } else if (typeof formatter === 'function') {\n return `<span data-formatter=\"${column.key}\">{{${path}}}</span>`;\n }\n }\n\n if (column.template) {\n return column.template;\n }\n\n // For editable cells, wrap content in a span for easy replacement\n if (column.editable) {\n return `<span class=\"cell-content\" data-field=\"${column.key}\">{{{${path}}}}</span>`;\n }\n\n return `{{{${path}}}}`;\n }\n\n /**\n * Build actions cell template\n */\n buildActionsTemplate() {\n if (!this.actions || this.actions.length === 0) return '';\n\n const buttons = this.actions.map(action => {\n if (typeof action === 'string') {\n switch (action) {\n case 'view':\n return `\n <button class=\"btn btn-sm btn-outline-primary\"\n data-action=\"view\"\n title=\"View\">\n <i class=\"bi bi-eye\"></i>\n </button>\n `;\n\n case 'edit':\n return `\n <button class=\"btn btn-sm btn-outline-secondary\"\n data-action=\"edit\"\n title=\"Edit\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n `;\n\n case 'delete':\n return `\n <button class=\"btn btn-sm btn-outline-danger\"\n data-action=\"delete\"\n title=\"Delete\">\n <i class=\"bi bi-trash\"></i>\n </button>\n `;\n\n default:\n return '';\n }\n } else if (typeof action === 'object') {\n return `\n <button class=\"btn btn-sm ${action.class || 'btn-outline-primary'}\"\n data-id=\"${this.model.id}\"\n data-action=\"${action.action}\"\n title=\"${action.label || ''}\">\n ${action.icon ? `<i class=\"${action.icon}\"></i>` : ''}\n ${action.label && !action.icon ? action.label : ''}\n </button>\n `;\n }\n return '';\n }).join('');\n\n return `<td><div class=\"btn-group btn-group-sm\">${buttons}</div></td>`;\n }\n\n /**\n * Build context menu cell template\n */\n buildContextMenuTemplate() {\n if (!this.contextMenu || this.contextMenu.length === 0) return '';\n\n return `\n <td class=\"text-end\" style=\"width: 1px;\">\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-link border-0\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n style=\"color: #6c757d;\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end shadow-sm\">\n ${this.buildContextMenuItems()}\n </ul>\n </div>\n </td>\n `;\n }\n\n /**\n * Build context menu items\n */\n buildContextMenuItems() {\n return this.contextMenu.map(menuItem => {\n if (menuItem.separator||menuItem.divider) {\n return '<li><hr class=\"dropdown-divider\"></li>';\n }\n\n let itemClass = 'dropdown-item';\n if (menuItem.action === 'delete' || menuItem.danger) {\n itemClass += ' text-danger';\n }\n if (menuItem.disabled) {\n itemClass += ' disabled';\n }\n\n return `\n <li>\n <a class=\"${itemClass}\" href=\"#\"\n data-id=\"{{model.id}}\"\n data-action=\"${menuItem.action}\"\n ${menuItem.disabled ? 'aria-disabled=\"true\" tabindex=\"-1\"' : ''}>\n ${menuItem.icon ? `<i class=\"${menuItem.icon} me-2\"></i>` : ''}\n ${menuItem.label}\n </a>\n </li>\n `;\n }).join('');\n }\n\n /**\n * Override onAfterRender to apply function formatters and templates\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Apply function formatters\n this.columns.forEach(column => {\n if (column.formatter && typeof column.formatter === 'function') {\n const cell = this.element.querySelector(`[data-formatter=\"${column.key}\"]`);\n if (cell) {\n const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n const context = {\n value,\n row: this.model, // deprecate this\n model: this.model,\n column,\n table: this.tableView,\n index: this.index\n };\n try {\n cell.innerHTML = column.formatter(value, context);\n } catch (error) {\n console.error(`Error formatting cell for column ${column.key}:`, error);\n }\n }\n }\n\n // Apply function templates\n // if (column.template && typeof column.template === 'function') {\n // const cell = this.element.querySelector(`[data-template=\"${column.key}\"]`);\n // if (cell) {\n // const value = this.model.get ? this.model.get(column.key) : this.model[column.key];\n // cell.innerHTML = column.template(value, this.model);\n // }\n // }\n });\n\n // Update selection state\n if (this.selected) {\n this.element.classList.add('selected');\n }\n\n // Set data-id attribute for easy identification\n const id = this.model.get ? this.model.get('id') : this.model.id;\n if (id) {\n this.element.setAttribute('data-id', id);\n }\n }\n\n /**\n * Handle edit cell action\n */\n async onActionEditCell(event, element) {\n event.stopPropagation();\n\n const columnKey = element.getAttribute('data-column');\n const column = this.columns.find(col => col.key === columnKey);\n\n if (!column || !column.editable) return;\n\n // Don't enter edit mode if already editing this cell\n if (this.editingCells.has(columnKey)) return;\n\n await this.enterEditMode(columnKey, column, element);\n }\n\n /**\n * Handle row click action\n */\n async onActionRowClick(event, element) {\n // Don't trigger row click if clicking on action buttons or editing\n if (event.target.closest('.btn-group') || event.target.closest('.dropdown') || event.target.closest('.cell-editor')) {\n return;\n }\n\n // Emit row click event\n this.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n\n // Notify parent TableView\n if (this.tableView) {\n this.tableView.emit('row:click', {\n row: this,\n model: this.model,\n column: element.getAttribute('data-column'),\n event: event\n });\n }\n }\n\n /**\n * Handle view action\n */\n async onActionView(event, element) {\n event.stopPropagation();\n\n this.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:view', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Handle edit action\n */\n async onActionEdit(event, element) {\n event.stopPropagation();\n\n this.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:edit', {\n row: this,\n model: this.model,\n event: event\n });\n }\n return true;\n }\n\n /**\n * Handle delete action\n */\n async onActionDelete(event, element) {\n event.stopPropagation();\n\n this.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n\n if (this.tableView) {\n this.tableView.emit('row:delete', {\n row: this,\n model: this.model,\n event: event\n });\n }\n }\n\n /**\n * Enter edit mode for a cell\n */\n async enterEditMode(columnKey, column, cellElement) {\n const contentSpan = cellElement.querySelector('.cell-content');\n if (!contentSpan) return;\n\n this.editingCells.add(columnKey);\n const currentValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Create editor based on column configuration\n const editor = this.createCellEditor(column, currentValue);\n\n // Replace content with editor\n const originalContent = contentSpan.innerHTML;\n contentSpan.style.display = 'none';\n\n const editorContainer = document.createElement('div');\n editorContainer.className = 'cell-editor';\n editorContainer.innerHTML = editor;\n cellElement.appendChild(editorContainer);\n\n // Focus the input\n const input = editorContainer.querySelector('input, select, .form-check-input');\n if (input) {\n input.focus();\n if (input.type === 'text' || input.type === 'textarea') {\n input.select();\n }\n }\n\n // Store original content for cancel\n editorContainer.dataset.originalContent = originalContent;\n editorContainer.dataset.columnKey = columnKey;\n\n // Set up event listeners\n this.setupEditorEvents(editorContainer, columnKey, column);\n\n this.emit('cell:edit', {\n row: this,\n model: this.model,\n column: columnKey,\n originalValue: currentValue\n });\n }\n\n /**\n * Create cell editor HTML based on column configuration\n */\n createCellEditor(column, currentValue) {\n const options = column.editableOptions || {};\n\n switch (options.type) {\n case 'select':\n return this.createSelectEditor(options, currentValue);\n case 'switch':\n case 'checkbox':\n return this.createSwitchEditor(options, currentValue);\n case 'textarea':\n return this.createTextareaEditor(options, currentValue);\n default:\n return this.createTextEditor(options, currentValue);\n }\n }\n\n /**\n * Create text input editor\n */\n createTextEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const inputType = options.inputType || 'text';\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <input type=\"${inputType}\"\n class=\"form-control form-control-sm cell-input\"\n value=\"${this.escapeHtml(currentValue || '')}\"\n placeholder=\"${placeholder}\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create textarea editor\n */\n createTextareaEditor(options, currentValue) {\n const placeholder = options.placeholder || '';\n const rows = options.rows || 2;\n\n return `\n <div class=\"d-flex gap-1\">\n <textarea class=\"form-control form-control-sm cell-input\"\n rows=\"${rows}\"\n placeholder=\"${placeholder}\">${this.escapeHtml(currentValue || '')}</textarea>\n <div class=\"d-flex flex-column gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Create select dropdown editor\n */\n createSelectEditor(options, currentValue) {\n const optionsArray = options.options || [];\n let optionsHtml = '';\n\n optionsArray.forEach(option => {\n if (typeof option === 'string') {\n const selected = option === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option}\" ${selected}>${option}</option>`;\n } else if (typeof option === 'object' && option.value !== undefined) {\n const selected = option.value === currentValue ? 'selected' : '';\n optionsHtml += `<option value=\"${option.value}\" ${selected}>${option.label || option.value}</option>`;\n }\n });\n\n return `\n <div class=\"d-flex gap-1 align-items-center\">\n <select class=\"form-select form-select-sm cell-input\">\n ${optionsHtml}\n </select>\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n `;\n }\n\n /**\n * Create switch/checkbox editor\n */\n createSwitchEditor(options, currentValue) {\n const checked = currentValue ? 'checked' : '';\n const switchType = options.type === 'switch' ? 'form-switch' : '';\n\n return `\n <div class=\"d-flex gap-2 align-items-center\">\n <div class=\"form-check ${switchType}\">\n <input class=\"form-check-input cell-input\" type=\"checkbox\" ${checked}>\n </div>\n <div class=\"d-flex gap-1\">\n <button type=\"button\" class=\"btn btn-sm btn-success cell-save\" title=\"Save\">\n <i class=\"bi bi-check\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary cell-cancel\" title=\"Cancel\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n `;\n }\n\n /**\n * Setup event listeners for cell editor\n */\n setupEditorEvents(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n const saveBtn = editorContainer.querySelector('.cell-save');\n const cancelBtn = editorContainer.querySelector('.cell-cancel');\n\n // Save on Enter (for text inputs)\n if (input && (input.type === 'text' || input.type === 'email' || input.type === 'number')) {\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n this.saveCellEdit(editorContainer, columnKey, column);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n this.cancelCellEdit(editorContainer, columnKey);\n }\n });\n }\n\n // Save on change for selects and checkboxes (if auto-save enabled)\n if (input && (input.type === 'checkbox' || input.tagName === 'SELECT') && column.autoSave !== false) {\n input.addEventListener('change', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n }\n\n // Button events\n saveBtn?.addEventListener('click', () => {\n this.saveCellEdit(editorContainer, columnKey, column);\n });\n\n cancelBtn?.addEventListener('click', () => {\n this.cancelCellEdit(editorContainer, columnKey);\n });\n }\n\n /**\n * Save cell edit\n */\n async saveCellEdit(editorContainer, columnKey, column) {\n const input = editorContainer.querySelector('.cell-input');\n if (!input) return;\n\n let newValue;\n\n // Extract value based on input type\n if (input.type === 'checkbox') {\n newValue = input.checked;\n } else if (input.tagName === 'SELECT') {\n newValue = input.value;\n } else {\n newValue = input.value;\n }\n\n const oldValue = this.model.get ? this.model.get(columnKey) : this.model[columnKey];\n\n // Save to model and backend\n try {\n if (this.model.save) {\n await this.model.save({ [columnKey]: newValue });\n } else {\n // Fallback for models without save method\n this.model[columnKey] = newValue;\n }\n\n // Exit edit mode\n this.exitEditMode(editorContainer, columnKey, newValue);\n\n // Emit save event\n this.emit('cell:save', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue\n });\n\n } catch (error) {\n // Show error and keep in edit mode\n console.error('Failed to save cell edit:', error);\n this.emit('cell:save:error', {\n row: this,\n model: this.model,\n column: columnKey,\n oldValue: oldValue,\n newValue: newValue,\n error: error\n });\n\n // Could show an error message in the UI\n editorContainer.classList.add('saving-error');\n setTimeout(() => editorContainer.classList.remove('saving-error'), 3000);\n }\n }\n\n /**\n * Cancel cell edit\n */\n cancelCellEdit(editorContainer, columnKey) {\n const originalContent = editorContainer.dataset.originalContent;\n this.exitEditMode(editorContainer, columnKey, null, originalContent);\n\n this.emit('cell:cancel', {\n row: this,\n model: this.model,\n column: columnKey\n });\n }\n\n /**\n * Exit edit mode and restore content\n */\n exitEditMode(editorContainer, columnKey, newValue = null, originalContent = null) {\n const cellElement = editorContainer.closest('td');\n const contentSpan = cellElement.querySelector('.cell-content');\n\n if (contentSpan) {\n if (newValue !== null) {\n // Update display with new value (with proper formatting if needed)\n const column = this.columns.find(col => col.key === columnKey);\n let displayValue = newValue;\n\n if (column && column.formatter && typeof column.formatter === 'string') {\n displayValue = dataFormatter.pipe(newValue, column.formatter);\n }\n\n contentSpan.innerHTML = this.escapeHtml(displayValue);\n } else if (originalContent) {\n // Restore original content on cancel\n contentSpan.innerHTML = originalContent;\n }\n\n contentSpan.style.display = '';\n }\n\n // Remove editor\n editorContainer.remove();\n this.editingCells.delete(columnKey);\n }\n\n /**\n * Escape HTML for safe display\n */\n escapeHtml(text) {\n if (text === null || text === undefined) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n\n /**\n * Override select to handle table-specific selection UI\n */\n select() {\n super.select();\n this.addClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.add('selected');\n }\n }\n\n /**\n * Override deselect to handle table-specific selection UI\n */\n deselect() {\n super.deselect();\n this.removeClass('selected');\n\n // Update checkbox visual state\n const selectCell = this.element?.querySelector('.mojo-select-cell');\n if (selectCell) {\n selectCell.classList.remove('selected');\n }\n }\n}\n\nexport default TableRow;\n","/**\n * DjangoLookups - Utility for Django-style filter lookup parsing and formatting\n * \n * Provides utilities to parse filter keys like \"status__in\" or \"created__gte\"\n * and format them into human-readable display text for filter pills.\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * formatFilterDisplay('status__in', 'new,open', 'Status') // \"Status in 'new', 'open'\"\n */\n\n/**\n * Supported Django-style lookups with display configurations\n * Only includes commonly used lookups (KISS principle)\n */\nexport const LOOKUPS = {\n // Comparison\n 'exact': { \n display: 'is',\n description: 'Exact match'\n },\n 'in': { \n display: 'in',\n description: 'Match any of the values (comma-separated)'\n },\n 'not': { \n display: 'is not',\n description: 'Does not match'\n },\n 'not_in': { \n display: 'not in',\n description: 'Does not match any of the values'\n },\n 'gt': { \n display: '>',\n description: 'Greater than'\n },\n 'gte': { \n display: '>=',\n description: 'Greater than or equal to'\n },\n 'lt': { \n display: '<',\n description: 'Less than'\n },\n 'lte': { \n display: '<=',\n description: 'Less than or equal to'\n },\n \n // String operations\n 'contains': { \n display: 'contains',\n description: 'Contains substring (case-sensitive)'\n },\n 'icontains': { \n display: 'contains',\n description: 'Contains substring (case-insensitive)'\n },\n 'startswith': { \n display: 'starts with',\n description: 'Starts with substring (case-sensitive)'\n },\n 'istartswith': { \n display: 'starts with',\n description: 'Starts with substring (case-insensitive)'\n },\n 'endswith': { \n display: 'ends with',\n description: 'Ends with substring (case-sensitive)'\n },\n 'iendswith': { \n display: 'ends with',\n description: 'Ends with substring (case-insensitive)'\n },\n \n // Null checks\n 'isnull': { \n display: (val) => val === 'true' || val === true ? 'is null' : 'is not null',\n description: 'Check if value is null or not'\n },\n \n // Range operations\n 'range': { \n display: 'between',\n description: 'Between two values (comma-separated)'\n }\n};\n\n/**\n * Parse a filter key into field name and lookup operator\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\", \"created__gte\")\n * @returns {Object} Object with field and lookup properties\n * \n * @example\n * parseFilterKey('status__in') // { field: 'status', lookup: 'in' }\n * parseFilterKey('status') // { field: 'status', lookup: null }\n * parseFilterKey('user__profile__name__icontains') // { field: 'user__profile__name', lookup: 'icontains' }\n */\nexport function parseFilterKey(paramKey) {\n if (!paramKey || typeof paramKey !== 'string') {\n return { field: paramKey, lookup: null };\n }\n\n const parts = paramKey.split('__');\n \n // Single part, no lookup\n if (parts.length === 1) {\n return { field: paramKey, lookup: null };\n }\n \n // Check if last part is a valid lookup\n const possibleLookup = parts[parts.length - 1];\n if (LOOKUPS[possibleLookup]) {\n return { \n field: parts.slice(0, -1).join('__'), \n lookup: possibleLookup \n };\n }\n \n // No valid lookup found, treat entire string as field name\n return { field: paramKey, lookup: null };\n}\n\n/**\n * Format a filter key and value into human-readable display text\n * \n * @param {string} paramKey - Filter parameter key (e.g., \"status__in\")\n * @param {string|Array} value - Filter value(s)\n * @param {string} label - Human-readable field label\n * @returns {string} Formatted display text\n * \n * @example\n * formatFilterDisplay('status__in', 'new,open', 'Status') \n * // \"Status in 'new', 'open'\"\n * \n * formatFilterDisplay('created__gte', '2025-01-01', 'Created') \n * // \"Created >= '2025-01-01'\"\n * \n * formatFilterDisplay('name__icontains', 'john', 'Name') \n * // \"Name contains 'john'\"\n */\nexport function formatFilterDisplay(paramKey, value, label) {\n if (!paramKey || value === null || value === undefined) {\n return '';\n }\n\n const { field, lookup } = parseFilterKey(paramKey);\n const lookupDef = LOOKUPS[lookup];\n \n // Convert array to comma-separated string if needed\n const valueStr = Array.isArray(value) ? value.join(',') : String(value);\n \n // No lookup or exact lookup - simple \"is\" format\n if (!lookup || lookup === 'exact') {\n return `${label} is '${valueStr}'`;\n }\n \n // Multi-value lookups (in, not_in)\n if (lookup === 'in' || lookup === 'not_in') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 0) {\n return `${label} ${lookupDef.display}`;\n }\n const formattedValues = values.map(v => `'${v}'`).join(', ');\n return `${label} ${lookupDef.display} ${formattedValues}`;\n }\n \n // Range lookup - special formatting\n if (lookup === 'range') {\n const values = valueStr.split(',').map(v => v.trim()).filter(v => v);\n if (values.length === 2) {\n return `${label} between '${values[0]}' and '${values[1]}'`;\n }\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Null check - dynamic display based on value\n if (lookup === 'isnull') {\n const displayText = typeof lookupDef.display === 'function' \n ? lookupDef.display(valueStr) \n : lookupDef.display;\n return `${label} ${displayText}`;\n }\n \n // Standard lookup with operator\n if (lookupDef) {\n return `${label} ${lookupDef.display} '${valueStr}'`;\n }\n \n // Fallback for unknown lookups\n return `${label} is '${valueStr}'`;\n}\n\n/**\n * Get a user-friendly description of a lookup operator\n * \n * @param {string} lookup - Lookup operator (e.g., \"in\", \"gte\", \"icontains\")\n * @returns {string} Human-readable description\n * \n * @example\n * getLookupDescription('in') // \"Match any of the values (comma-separated)\"\n * getLookupDescription('gte') // \"Greater than or equal to\"\n */\nexport function getLookupDescription(lookup) {\n const lookupDef = LOOKUPS[lookup];\n return lookupDef ? lookupDef.description : 'Exact match';\n}\n\n/**\n * Check if a string is a valid lookup operator\n * \n * @param {string} lookup - Potential lookup operator\n * @returns {boolean} True if valid lookup\n * \n * @example\n * isValidLookup('in') // true\n * isValidLookup('foo') // false\n */\nexport function isValidLookup(lookup) {\n return lookup && LOOKUPS.hasOwnProperty(lookup);\n}\n\n/**\n * Get all available lookup operators\n * \n * @returns {Array<string>} Array of lookup operator names\n * \n * @example\n * getAvailableLookups() // ['exact', 'in', 'not', 'not_in', 'gt', ...]\n */\nexport function getAvailableLookups() {\n return Object.keys(LOOKUPS);\n}\n\n/**\n * Build a filter key from field name and lookup operator\n * \n * @param {string} field - Field name\n * @param {string} lookup - Lookup operator (optional)\n * @returns {string} Combined filter key\n * \n * @example\n * buildFilterKey('status', 'in') // \"status__in\"\n * buildFilterKey('status') // \"status\"\n */\nexport function buildFilterKey(field, lookup = null) {\n if (!field) return '';\n if (!lookup) return field;\n return `${field}__${lookup}`;\n}\n\nexport default {\n LOOKUPS,\n parseFilterKey,\n formatFilterDisplay,\n getLookupDescription,\n isValidLookup,\n getAvailableLookups,\n buildFilterKey\n};\n","/**\n * TableView - Advanced data table component extending ListView\n *\n * Leverages ListView's view management system for efficient row rendering.\n * Each row is a separate TableRow view that only re-renders when its model changes.\n *\n * @example\n * const table = new TableView({\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email', visibility: 'md' }, // Hidden on xs/sm, visible md+\n * { key: 'phone', label: 'Phone', visibility: 'lg' }, // Visible only on lg+\n * { key: 'created', label: 'Created', formatter: 'date', visibility: 'xl' } // Visible only on xl+\n * ],\n * actions: ['view', 'edit', 'delete'],\n * selectionMode: 'multiple'\n * });\n */\n\nimport ListView from '../list/ListView.js';\nimport TableRow from './TableRow.js';\nimport Mustache from '@core/utils/mustache.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport FormView from '@core/forms/FormView.js';\nimport dataFormatter from '@core/utils/DataFormatter.js';\nimport { parseFilterKey, formatFilterDisplay } from '@core/utils/DjangoLookups.js';\n\nclass TableView extends ListView {\n constructor(options = {}) {\n // Set up table-specific defaults before calling super\n const tableOptions = {\n className: 'table-view-component',\n itemClass: options.itemClass || TableRow,\n selectionMode: options.selectable ? 'multiple' : 'none',\n emptyMessage: options.emptyMessage || 'No data available',\n addButtonIcon: options.addButtonIcon || 'bi bi-plus-circle',\n ...options\n };\n\n super(tableOptions);\n\n // Fullscreen state\n this.isFullscreen = false;\n\n // Table-specific properties\n this.columns = options.columns || [];\n this.actions = options.actions || null;\n this.contextMenu = options.contextMenu || null;\n this.batchActions = options.batchActions || null;\n this.searchable = options.searchable !== false;\n this.sortable = options.sortable !== false;\n this.filterable = options.filterable !== false;\n this.paginated = options.paginated !== false;\n this.clickAction = options.clickAction || \"view\";\n\n // Model operation configurations\n this.itemView = options.itemView;\n this.addForm = options.addForm;\n this.editForm = options.editForm;\n this.deleteTemplate = options.deleteTemplate;\n this.formDialogConfig = options.formDialogConfig || {};\n this.viewDialogOptions = options.viewDialogOptions || {};\n\n // Export configuration\n this.exportOptions = options.exportOptions || null;\n if (this.options.showExport && !this.exportOptions) {\n this.exportOptions = [\n { format: 'csv', label: 'Export as CSV', icon: 'bi bi-file-earmark-spreadsheet' },\n { format: 'json', label: 'Export as JSON', icon: 'bi bi-file-earmark-code' }\n ];\n }\n this.exportSource = options.exportSource || 'remote';\n\n // Filter configuration\n this.filters = {};\n this.additionalFilters = options.filters || [];\n this.hideActivePills = options.hideActivePills || false;\n this.hideActivePillNames = options.hideActivePillNames || [];\n this.rowAction = options.rowAction || \"row-click\";\n this.batchBarLocation = options.batchBarLocation || \"bottom\"; // \"top\" or \"bottom\"\n\n this.options.addButtonLabel = options.addButtonLabel || 'Add';\n\n // Custom toolbar buttons\n this.toolbarButtons = options.toolbarButtons || [];\n\n // Table display options\n this.tableOptions = {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n size: null, // null, 'sm', 'lg'\n ...options.tableOptions\n };\n\n // Search configuration\n this.searchPlacement = options.searchPlacement || 'toolbar'; // 'toolbar' or 'dropdown'\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n\n // Initialize column configuration BEFORE building template\n this.initializeColumns();\n\n // Extract filters from columns BEFORE building template\n this.extractColumnFilters();\n\n // Detect columns that need footer totals\n this.footerTotalColumns = this.columns.filter(col => col.footer_total === true);\n this.hasFooterTotals = this.footerTotalColumns.length > 0;\n\n // Build template with Mustache variables\n this.template = this.buildTableTemplate();\n\n // Listen for collection changes to update totals\n this.setupCollectionListeners();\n }\n\n /**\n * Setup collection event listeners for totals updates\n */\n setupCollectionListeners() {\n if (this.hasFooterTotals && this.collection) {\n // Re-render totals when collection data changes\n this.collection.on('reset add remove change', () => {\n this.updateFooterTotals();\n });\n }\n }\n\n /**\n * Initialize column configuration\n */\n initializeColumns() {\n this.columns.forEach(column => {\n // Ensure each column has a key\n if (!column.key && column.name) {\n column.key = column.name;\n }\n\n // Set default label if not provided\n if (!column.label && !column.title) {\n column.label = column.key.charAt(0).toUpperCase() + column.key.slice(1);\n }\n });\n }\n\n /**\n * Get responsive CSS classes for column visibility\n * @param {string|object} visibility - Bootstrap breakpoint or config object\n * - String: 'md' = show at md and up (hide below)\n * - Object: { hide: 'md' } = hide at md and up (show below)\n * - Object: { show: 'md', hide: 'lg' } = show from md to lg only\n * @returns {string} Bootstrap responsive display classes\n */\n getResponsiveClasses(visibility) {\n if (!visibility) return ''; // Always visible if no visibility specified\n\n const validBreakpoints = ['sm', 'md', 'lg', 'xl', 'xxl'];\n\n // Legacy string format: show at breakpoint and up\n if (typeof visibility === 'string') {\n if (!validBreakpoints.includes(visibility)) {\n console.warn(`Invalid visibility breakpoint: ${visibility}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n return `d-none d-${visibility}-table-cell`;\n }\n\n // Object format for more control\n if (typeof visibility === 'object') {\n const classes = [];\n\n // Hide at breakpoint and up\n if (visibility.hide) {\n if (!validBreakpoints.includes(visibility.hide)) {\n console.warn(`Invalid hide breakpoint: ${visibility.hide}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n classes.push(`d-table-cell d-${visibility.hide}-none`);\n }\n\n // Show at breakpoint and up (optionally combined with hide)\n if (visibility.show) {\n if (!validBreakpoints.includes(visibility.show)) {\n console.warn(`Invalid show breakpoint: ${visibility.show}. Valid options are: ${validBreakpoints.join(', ')}`);\n return '';\n }\n if (!visibility.hide) {\n classes.push(`d-none d-${visibility.show}-table-cell`);\n } else {\n classes.push(`d-${visibility.show}-table-cell`);\n }\n }\n\n return classes.join(' ');\n }\n\n return '';\n }\n\n /**\n * Extract column key and formatter from combined key (e.g., \"sales_amount|currency\")\n */\n parseColumnKey(key) {\n const parts = key.split('|');\n return {\n fieldKey: parts[0],\n formatter: parts[1] || null\n };\n }\n\n /**\n * Update footer totals in the DOM without full re-render\n */\n updateFooterTotals() {\n if (!this.hasFooterTotals || !this.element) return;\n\n const totals = this.calculateFooterTotals();\n console.log('Updating footer totals in DOM:', totals);\n\n // Update each total cell in the footer\n let totalColumnIndex = 0;\n this.columns.forEach((column) => {\n if (column.footer_total) {\n const safeKey = `col_${totalColumnIndex}`;\n const cell = this.element.querySelector(`[data-total-column=\"${safeKey}\"]`);\n\n if (cell && totals[safeKey]) {\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n let displayValue;\n\n if (formatter && typeof formatter === 'string') {\n // Use DataFormatter if available\n displayValue = this.formatValue(totals[safeKey].value, formatter);\n } else {\n displayValue = totals[safeKey].value;\n }\n\n cell.textContent = displayValue;\n }\n totalColumnIndex++;\n }\n });\n }\n\n /**\n * Format a value using DataFormatter\n */\n formatValue(value, formatter) {\n try {\n return dataFormatter.pipe(value, formatter);\n } catch (e) {\n console.warn('Error formatting value:', e);\n return value;\n }\n }\n\n /**\n * Calculate totals for footer columns\n */\n calculateFooterTotals() {\n if (!this.hasFooterTotals || !this.collection || this.collection.length === 0) {\n return {};\n }\n\n const totals = {};\n\n this.footerTotalColumns.forEach((column, totalColumnIndex) => {\n const { fieldKey, formatter } = this.parseColumnKey(column.key);\n let sum = 0;\n\n // Sum values from all items in collection\n this.collection.forEach(model => {\n const value = model.get ? model.get(fieldKey) : model[fieldKey];\n const numValue = parseFloat(value) || 0;\n sum += numValue;\n });\n\n // Debug logging\n console.log(`Footer total for ${column.key}: ${sum} (from ${this.collection.length} items)`);\n\n // Use safe key for Mustache (avoid special characters)\n const safeKey = `col_${totalColumnIndex}`;\n\n // Store total with formatter info\n totals[safeKey] = {\n value: sum,\n formatter: formatter || column.formatter,\n fieldKey: fieldKey,\n originalKey: column.key\n };\n });\n\n return totals;\n }\n\n /**\n * Extract filters from column configuration\n */\n extractColumnFilters() {\n this.filters = {};\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n this.filters[fieldKey] = column.filter;\n }\n });\n }\n\n isSelectable() {\n return this.batchActions && this.batchActions.length > 0 && this.selectionMode == 'multiple';\n }\n\n /**\n * Build the complete table template\n */\n buildTableTemplate() {\n const batchPanelTop = this.batchBarLocation === 'top' ? this.buildBatchActionsPanel() : '';\n const batchPanelBottom = this.batchBarLocation === 'bottom' ? this.buildBatchActionsPanel() : '';\n\n return `\n <div class=\"mojo-table-wrapper\">\n ${this.buildToolbarTemplate()}\n ${batchPanelTop}\n <div class=\"table-container\"${(() => { const __fs = (this.tableOptions && this.tableOptions.fontSize != null) ? this.tableOptions.fontSize : (this.options && this.options.fontSize); const __val = __fs === 'sm' ? '0.9rem' : (__fs === 'xs' ? '0.8rem' : (__fs ? String(__fs) : null)); return __val ? ` style=\"font-size: ${__val};\"` : ''; })()}>\n {{#loading}}\n <div class=\"mojo-table-loading d-flex justify-content-center align-items-center py-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n {{/loading}}\n {{^loading}}\n {{#isEmpty}}\n <div class=\"table-empty text-center py-5\">\n <i class=\"bi bi-inbox fa-2x mb-2 text-muted\"></i>\n <p class=\"text-muted\">{{emptyMessage}}</p>\n </div>\n {{/isEmpty}}\n {{^isEmpty}}\n <table class=\"${this.buildTableClasses()}\">\n ${this.buildTableHeaderTemplate()}\n <tbody data-container=\"items\"></tbody>\n ${this.hasFooterTotals ? this.buildTableFooterTemplate() : ''}\n </table>\n {{/isEmpty}}\n {{/loading}}\n </div>\n ${batchPanelBottom}\n ${this.buildPaginationTemplate()}\n </div>\n `;\n }\n\n /**\n * Build table CSS classes\n */\n buildTableClasses() {\n let classes = ['table'];\n\n if (this.tableOptions.striped) classes.push('table-striped');\n if (this.tableOptions.bordered) classes.push('table-bordered');\n if (this.tableOptions.hover) classes.push('table-hover');\n if (this.tableOptions.responsive) classes.push('table-responsive');\n if (this.tableOptions.background) classes.push(`table-${this.tableOptions.background}`);\n if (this.tableOptions.size === 'sm') classes.push('table-sm');\n if (this.tableOptions.size === 'lg') classes.push('table-lg');\n\n return classes.join(' ');\n }\n\n /**\n * Build toolbar template\n */\n buildToolbarTemplate() {\n if (!this.searchable && !this.filterable) {\n return '';\n }\n\n return `\n <div class=\"table-action-buttons mb-3\">\n <div class=\"d-flex align-items-center gap-2\">\n ${this.buildActionButtonsTemplate()}\n ${this.filterable ? this.buildFilterDropdownTemplate() : ''}\n ${this.searchable && this.searchPlacement === 'toolbar' ? this.buildSearchTemplate() : ''}\n\n </div>\n <div data-container=\"filter-pills\"></div>\n </div>\n `;\n }\n\n /**\n * Build action buttons template\n */\n buildActionButtonsTemplate() {\n let buttons = [];\n\n // Refresh button\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-refresh\"\n data-action=\"refresh\"\n title=\"Refresh\">\n <i class=\"bi bi-arrow-clockwise\"></i>\n </button>\n `);\n\n // Fullscreen button (only if browser supports it)\n if (this.isFullscreenSupported()) {\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-fullscreen\"\n data-action=\"toggle-fullscreen\"\n title=\"Toggle Fullscreen\">\n <i class=\"bi bi-fullscreen\"></i>\n </button>\n `);\n }\n\n // Custom action buttons from options\n if (this.options.showAdd) {\n buttons.push(`\n <button class=\"btn btn-sm btn-success btn-add\"\n data-action=\"add\"\n title=\"${this.options.addButtonLabel}\">\n <i class=\"${this.options.addButtonIcon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${this.options.addButtonLabel}</span>\n </button>\n `);\n }\n\n if (this.options.showExport) {\n if (this.exportOptions && this.exportOptions.length > 1) {\n // Dropdown for multiple export options\n const dropdownItems = this.exportOptions.map(opt => `\n <li>\n <a class=\"dropdown-item\" href=\"#\" data-action=\"export\" data-format=\"${opt.format}\">\n <i class=\"${opt.icon || 'bi bi-file-earmark-arrow-down'} me-2\"></i>${opt.label}\n </a>\n </li>\n `).join('');\n\n buttons.push(`\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\" title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n <ul class=\"dropdown-menu\">\n ${dropdownItems}\n </ul>\n </div>\n `);\n } else {\n // Single export button\n const format = this.exportOptions && this.exportOptions.length === 1 ? this.exportOptions[0].format : 'json';\n buttons.push(`\n <button class=\"btn btn-sm btn-outline-secondary btn-export\"\n data-action=\"export\"\n data-format=\"${format}\"\n title=\"Export\">\n <i class=\"bi bi-download me-1\"></i>\n <span class=\"d-none d-lg-inline\">Export</span>\n </button>\n `);\n }\n }\n\n // if (buttons.length > 0) {\n // buttons.push(`<div class=\"vr mx-2\"></div>`);\n // }\n\n // Render custom toolbar buttons\n if (this.toolbarButtons && this.toolbarButtons.length > 0) {\n this.toolbarButtons.forEach((button, index) => {\n const {\n label = 'Button',\n icon = '',\n action = '',\n handler = null,\n variant = 'outline-secondary',\n title = label,\n className = '',\n permissions = null\n } = button;\n\n // Check permissions if specified\n if (permissions && !this.checkPermissions(permissions)) {\n return;\n }\n\n const iconHtml = icon ? `<i class=\"${icon} me-1\"></i>` : '';\n const labelHtml = `<span class=\"d-none d-lg-inline\">${label}</span>`;\n\n // Use handler if provided, otherwise use action for data-action attribute\n let dataAttrs = '';\n if (handler) {\n dataAttrs = `data-action=\"custom-toolbar-button\" data-button-index=\"${index}\"`;\n } else if (action) {\n dataAttrs = `data-action=\"${action}\"`;\n }\n\n const btnClass = `btn btn-sm btn-${variant} ${className}`.trim();\n\n buttons.push(`\n <button class=\"${btnClass}\"\n ${dataAttrs}\n title=\"${title}\">\n ${iconHtml}${labelHtml}\n </button>\n `);\n });\n }\n\n return buttons.join('');\n }\n\n /**\n * Build search template\n */\n buildSearchTemplate() {\n return `\n <div class=\"flex-grow-1\" style=\"max-width: 400px;\">\n <div class=\"input-group input-group-sm\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input type=\"search\"\n class=\"form-control\"\n placeholder=\"{{searchPlaceholder}}\"\n data-filter=\"search\"\n data-change-action=\"apply-search\"\n value=\"{{collection.params.search}}\"\n aria-label=\"Search\">\n {{#searchValue}}\n <button class=\"btn btn-outline-secondary\" type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\">\n <i class=\"bi bi-x\"></i>\n </button>\n {{/searchValue}}\n </div>\n </div>\n `;\n }\n\n /**\n * Build filter dropdown template\n */\n buildFilterDropdownTemplate() {\n const hasFilters = (this.filters && Object.keys(this.filters).length > 0) ||\n (this.additionalFilters && this.additionalFilters.length > 0);\n\n if (!hasFilters) {\n return '';\n }\n\n return `\n <div class=\"dropdown\">\n <button class=\"btn btn-sm btn-outline-secondary dropdown-toggle\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n <i class=\"bi bi-filter me-1\"></i>\n <span class=\"d-none d-lg-inline\">Add Filter</span>\n </button>\n <div class=\"dropdown-menu\" style=\"min-width: 250px;\">\n ${this.buildFilterList()}\n </div>\n </div>\n `;\n }\n\n /**\n * Build simple filter selection list\n */\n buildFilterList() {\n const allFilters = this.getAllAvailableFilters();\n const activeFilters = this.getActiveFilters();\n\n if (allFilters.length === 0) {\n return '<div class=\"dropdown-item-text text-muted\">No filters available</div>';\n }\n\n const filterItems = allFilters.map(filter => {\n const isActive = activeFilters.hasOwnProperty(filter.key);\n const activeClass = isActive ? 'active' : '';\n const icon = this.getFilterIcon(filter.type || filter.config?.type);\n\n return `\n <button class=\"dropdown-item ${activeClass}\"\n data-action=\"add-filter\"\n data-filter-key=\"${filter.key}\">\n <i class=\"bi bi-${icon} me-2\"></i>\n ${filter.label}\n ${isActive ? '<i class=\"bi bi-check-circle ms-auto\"></i>' : ''}\n </button>\n `;\n }).join('');\n\n return `\n ${filterItems}\n ${Object.keys(activeFilters).length > 0 ? `\n <div class=\"dropdown-divider\"></div>\n <button class=\"dropdown-item text-danger\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-2\"></i>Clear All Filters\n </button>\n ` : ''}\n `;\n }\n\n /**\n * Update filter pills in the DOM\n */\n updateFilterPills() {\n const container = this.element?.querySelector('[data-container=\"filter-pills\"]');\n\n if (!container) {\n return;\n }\n\n const activeFilters = this.getActiveFilters();\n\n const pillsHTML = this.buildActivePills();\n container.innerHTML = pillsHTML;\n }\n\n /**\n * Update search input value across all search inputs\n */\n updateSearchInputs(value) {\n const searchInputs = this.element?.querySelectorAll('[data-filter=\"search\"]');\n if (searchInputs) {\n searchInputs.forEach(input => {\n input.value = value || '';\n });\n }\n }\n\n /**\n * Build active filter pills display\n */\n buildActivePills() {\n if (this.hideActivePills) {\n return '';\n }\n\n const activeFilters = this.getActiveFilters();\n const hasSearch = activeFilters.search && activeFilters.search.toString().trim() !== '';\n let filterEntries = Object.entries(activeFilters).filter(([key, value]) =>\n value && value.toString().trim() !== '' && key !== 'search'\n );\n\n // Hide specific pills based on configuration\n if (this.hideActivePillNames && this.hideActivePillNames.length > 0) {\n filterEntries = filterEntries.filter(([key]) =>\n !this.hideActivePillNames.includes(key)\n );\n }\n\n if (filterEntries.length === 0 && !hasSearch) {\n return '';\n }\n\n const pills = filterEntries.map(([paramKey, value]) => {\n const { field } = parseFilterKey(paramKey);\n const label = this.getFilterLabel(field);\n const displayText = formatFilterDisplay(paramKey, value, label);\n const icon = 'filter'; // search won't appear as pill anymore\n\n return `\n <span class=\"badge bg-primary me-1 mb-1 py-1 px-2 position-relative\" style=\"font-size: 0.75rem;\">\n <i class=\"bi bi-${icon} me-1\" style=\"font-size: 0.65rem;\"></i>\n\n <button type=\"button\" class=\"btn btn-link text-white p-0 ms-1\"\n style=\"font-size: 0.65rem; line-height: 1;\"\n data-action=\"edit-filter\"\n data-filter=\"${paramKey}\"\n title=\"Edit filter\">\n ${displayText}\n </button>\n\n <button type=\"button\" class=\"btn-close btn-close-white ms-1\"\n style=\"font-size: 0.6rem; width: 0.5rem; height: 0.5rem;\"\n data-action=\"remove-filter\"\n data-filter=\"${paramKey}\"\n title=\"Remove filter\">\n </button>\n </span>\n `;\n }).join('');\n\n // Show Clear All if there are multiple filters, or any filter + search\n const showClearAll = filterEntries.length > 1 || (filterEntries.length > 0 && hasSearch) || (filterEntries.length === 0 && hasSearch);\n const clearAllButton = showClearAll ? `\n <button class=\"btn btn-sm btn-outline-secondary mb-1 py-0 px-2\" style=\"font-size: 0.75rem;\" data-action=\"clear-all-filters\">\n <i class=\"bi bi-x-circle me-1\" style=\"font-size: 0.7rem;\"></i>\n <small>Clear All</small>\n </button>\n ` : '';\n\n return `\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"d-flex flex-wrap align-items-center\">\n ${pills}\n ${clearAllButton}\n </div>\n </div>\n </div>\n `;\n }\n\n /**\n * Build table header template\n */\n buildTableHeaderTemplate() {\n let headerCells = '';\n\n // Selection checkbox header\n if (this.isSelectable()) {\n headerCells += `\n <th style=\"width: 40px; padding: 0;\">\n <div class=\"mojo-select-all-cell\" data-action=\"select-all\">\n <div class=\"mojo-checkbox\">\n <i class=\"bi bi-check\"></i>\n </div>\n </div>\n </th>\n `;\n }\n\n // Column headers\n this.columns.forEach(column => {\n // Parse column key to get field name without pipes/formatters\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const sortable = this.sortable && column.sortable !== false;\n const currentSort = this.getSortBy() === fieldKey ? this.getSortDirection() : null;\n const sortIcon = this.getSortIcon(currentSort);\n const label = column.label || column.title || fieldKey;\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n const sortDropdown = sortable ? `\n <div class=\"dropdown d-inline-block ms-2\">\n <button class=\"btn btn-sm btn-link p-0 text-decoration-none\" type=\"button\"\n data-bs-toggle=\"dropdown\" aria-expanded=\"false\"\n data-column=\"${fieldKey}\">\n ${sortIcon}\n </button>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n <li><a class=\"dropdown-item ${currentSort === 'asc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"asc\">\n <i class=\"bi bi-sort-alpha-down me-2\"></i>Sort A-Z\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === 'desc' ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"desc\">\n <i class=\"bi bi-sort-alpha-down-alt me-2\"></i>Sort Z-A\n </a></li>\n <li><a class=\"dropdown-item ${currentSort === null ? 'active' : ''}\"\n data-action=\"sort\" data-field=\"${fieldKey}\" data-direction=\"none\">\n <i class=\"bi bi-x-circle me-2\"></i>No Sort\n </a></li>\n </ul>\n </div>\n ` : '';\n\n headerCells += `\n <th class=\"${sortable ? 'sortable' : ''} ${responsiveClasses}\">\n <div class=\"d-flex align-items-center\">\n <span>${label}</span>\n ${sortDropdown}\n </div>\n </th>\n `;\n });\n\n // Actions header\n if (this.actions) {\n headerCells += '<th>Actions</th>';\n } else if (this.contextMenu) {\n headerCells += '<th style=\"width: 1px;\"></th>';\n }\n\n return `\n <thead>\n <tr>\n ${headerCells}\n </tr>\n </thead>\n `;\n }\n\n /**\n * Build table footer template with totals\n */\n buildTableFooterTemplate() {\n let footerCells = '';\n\n // Selection checkbox footer (empty)\n if (this.isSelectable()) {\n footerCells += '<td></td>';\n }\n\n // Column footers\n let totalColumnIndex = 0;\n this.columns.forEach((column, index) => {\n const responsiveClasses = this.getResponsiveClasses(column.visibility);\n\n if (column.footer_total) {\n // Use safe key for Mustache template\n const safeKey = `col_${totalColumnIndex}`;\n const formatter = this.parseColumnKey(column.key).formatter || column.formatter;\n\n let cellContent;\n if (formatter && typeof formatter === 'string') {\n cellContent = `{{{footerTotals.${safeKey}.value|${formatter}}}}`;\n } else {\n cellContent = `{{footerTotals.${safeKey}.value}}`;\n }\n\n footerCells += `<td class=\"table-footer-total ${responsiveClasses}\" data-total-column=\"${safeKey}\">${cellContent}</td>`;\n totalColumnIndex++;\n } else if (index === 0) {\n // First column shows \"Totals\" label\n footerCells += `<td class=\"table-footer-label ${responsiveClasses}\"><strong>Totals</strong></td>`;\n } else {\n // Empty cell for non-total columns\n footerCells += `<td class=\"${responsiveClasses}\"></td>`;\n }\n });\n\n // Actions footer (empty)\n if (this.actions) {\n footerCells += '<td></td>';\n } else if (this.contextMenu) {\n footerCells += '<td></td>';\n }\n\n return `\n <tfoot>\n <tr class=\"table-totals-row\">\n ${footerCells}\n </tr>\n </tfoot>\n `;\n }\n\n /**\n * Build batch actions panel\n */\n buildBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) {\n return '';\n }\n\n if (this.batchBarLocation === 'top') {\n // Toolbar-style batch actions for top placement\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"batch-${action.action}\" title=\"${action.label}\">\n <i class=\"${action.icon} me-1\"></i>\n <span class=\"d-none d-lg-inline\">${action.label}</span>\n </button>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel-top alert alert-info d-none mb-3\" role=\"alert\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div class=\"d-flex align-items-center\">\n <strong class=\"me-2\">\n <span class=\"batch-select-count\">0</span> ${this.options.batchPanelTitle || 'items'} selected\n </strong>\n </div>\n <div class=\"d-flex gap-2 align-items-center\">\n ${actionsHTML}\n <button class=\"btn btn-sm btn-outline-secondary\" data-action=\"clear-selection\" title=\"Clear Selection\">\n <i class=\"bi bi-x-circle me-1\"></i>\n <span class=\"d-none d-lg-inline\">Clear</span>\n </button>\n </div>\n </div>\n </div>\n `;\n } else {\n // Original bottom panel style\n let actionsHTML = '';\n this.batchActions.forEach(action => {\n actionsHTML += `\n <div class=\"batch-select-action text-center px-2\" data-action=\"batch-${action.action}\">\n <div class=\"batch-action-icon fs-3\">\n <i class=\"${action.icon}\"></i>\n </div>\n <div class=\"batch-action-title small\">${action.label}</div>\n </div>\n `;\n });\n\n return `\n <div class=\"batch-actions-panel rounded-start rounded-end\" style=\"display: none;\">\n <div class=\"batch-select-panel rounded-start rounded-end\">\n <div class=\"row g-0\">\n <div class=\"col-auto\">\n <div class=\"batch-select-count rounded-start\">0</div>\n </div>\n <div class=\"col\">\n <div class=\"ps-2 batch-select-title\">${this.options.batchPanelTitle || 'Rows'}</div>\n </div>\n <div class=\"col\">\n <div class=\"batch-select-actions d-flex justify-content-end\">\n ${actionsHTML}\n </div>\n </div>\n <div class=\"col-auto\">\n <div class=\"batch-select-end rounded-end\"></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n }\n\n /**\n * Build pagination template\n */\n buildPaginationTemplate() {\n if (!this.paginated) {\n return '';\n }\n\n return `\n <div class=\"table-status-bar mt-3\">\n <div class=\"d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3\">\n <div class=\"d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start\">\n <span class=\"text-muted\">\n Showing <span data-value=\"start\">0</span> to <span data-value=\"end\">0</span>\n of <span data-value=\"total\">0</span> entries\n </span>\n <div class=\"d-flex align-items-center\">\n <label class=\"form-label me-2 mb-0\">Show:</label>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\" data-change-action=\"page-size\">\n <option value=\"5\">5</option>\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n </div>\n <nav aria-label=\"Table pagination\">\n <ul class=\"pagination pagination-sm mb-0 justify-content-center\" data-container=\"pagination\">\n <!-- Pagination will be rendered here -->\n </ul>\n </nav>\n </div>\n </div>\n `;\n }\n\n /**\n * Override _createItemView to pass table-specific options\n */\n _createItemView(model, index) {\n const itemView = new this.itemClass({\n model: model,\n index: index,\n listView: this,\n tableView: this, // Also pass as tableView for clarity\n template: this.itemTemplate,\n columns: this.columns,\n actions: this.actions,\n contextMenu: this.contextMenu,\n batchActions: this.batchActions,\n containerId: 'items'\n });\n\n // Store the item view\n this.itemViews.set(model.id, itemView);\n\n // Set up item event listeners\n itemView.on('item:select', (event) => {\n this._onItemSelect(event);\n this.updateBatchActionsPanel();\n });\n itemView.on('item:deselect', (event) => {\n this._onItemDeselect(event);\n this.updateBatchActionsPanel();\n });\n\n // Table-specific row events\n itemView.on('row:click', this._onRowClick.bind(this));\n itemView.on('row:view', this._onRowView.bind(this));\n itemView.on('row:edit', this._onRowEdit.bind(this));\n itemView.on('row:delete', this._onRowDelete.bind(this));\n itemView.on('cell:edit', this._onCellEdit.bind(this));\n itemView.on('cell:save', this._onCellSave.bind(this));\n itemView.on('cell:cancel', this._onCellCancel.bind(this));\n\n return itemView;\n }\n\n /**\n * Override onMounted to ensure filter pills are shown on initial load\n */\n async onMounted() {\n await super.onMounted();\n const activeFilters = this.getActiveFilters();\n\n // Ensure filter pills are displayed if there are active filters from URL\n if (this.collection && Object.keys(activeFilters).length > 0) {\n this.updateFilterPills();\n }\n\n // Add listener for native search clear button\n this.setupSearchClearListener();\n }\n\n /**\n * Setup listener for native search clear (X) button\n */\n setupSearchClearListener() {\n if (!this.element) return;\n\n const searchInputs = this.element.querySelectorAll('input[type=\"search\"][data-filter=\"search\"]');\n searchInputs.forEach(input => {\n // Listen for input event to detect native clear\n input.addEventListener('input', (event) => {\n // If value is empty and we had a search before, it was cleared\n if (event.target.value === '' && this.getActiveFilters().search) {\n this.onActionClearSearch(event, event.target);\n }\n });\n });\n }\n\n /**\n * Handle row click event\n */\n _onRowClick(event) {\n this.emit('row:click', event);\n\n // Default behavior - show item details if configured\n if (this.options.onRowClick) {\n return this.options.onRowClick(event.model, event.event);\n }\n\n if (this.clickAction === 'view') {\n this._onRowView(event);\n } else if (this.clickAction === 'edit') {\n this._onRowEdit(event);\n }\n }\n\n /**\n * Get the Model class from collection or instance\n */\n getModelClass(model) {\n // Try to get from collection first\n if (this.collection?.ModelClass) return this.collection.ModelClass;\n if (this.collection?.model) return this.collection.model;\n\n // Try to get from a model instance\n if (model?.constructor) return model.constructor;\n\n // Return null if we can't determine\n return null;\n }\n\n /**\n * Get model name for display\n */\n getModelName(model) {\n const ModelClass = this.getModelClass(model);\n if (!ModelClass) return 'Item';\n\n return ModelClass.MODEL_NAME ||\n ModelClass.name.replace(/Model$/, '') ||\n 'Item';\n }\n\n /**\n * Resolve item view class with fallbacks\n */\n getItemViewClass(model) {\n // Check instance options first\n if (this.itemView) return this.itemView;\n\n // Check Model class static property\n const ModelClass = this.getModelClass(model);\n if (ModelClass?.VIEW_CLASS) return ModelClass.VIEW_CLASS;\n\n return null; // Will use data view as fallback\n }\n\n /**\n * Resolve add form configuration with fallbacks\n */\n getAddFormConfig(ModelClass) {\n return this.addForm ||\n ModelClass?.ADD_FORM ||\n this.editForm ||\n ModelClass?.EDIT_FORM;\n }\n\n /**\n * Resolve edit form configuration with fallbacks\n */\n getEditFormConfig(ModelClass) {\n return this.editForm ||\n ModelClass?.EDIT_FORM ||\n this.addForm ||\n ModelClass?.ADD_FORM;\n }\n\n /**\n * Get form dialog configuration\n */\n getFormDialogConfig(ModelClass) {\n return {\n ...ModelClass?.FORM_DIALOG_CONFIG,\n ...this.formDialogConfig\n };\n }\n\n /**\n * Render a template string with model context\n */\n renderTemplateString(template, model) {\n if (!template) return '';\n\n // Use Mustache to render the template with the model as context\n return Mustache.render(template, model);\n }\n\n /**\n * Handle row view action\n */\n async _onRowView(event) {\n this.emit('row:view', event);\n\n // Check for custom handler first\n if (this.options.onItemView) {\n await this.options.onItemView(event.model, event.event);\n return;\n }\n\n const ViewClass = this.getItemViewClass(event.model);\n\n if (ViewClass) {\n // Use custom view class\n const viewInstance = new ViewClass({ model: event.model });\n await Dialog.showDialog({\n header: false,\n body: viewInstance,\n size: 'lg',\n centered: false,\n ...this.getFormDialogConfig(this.getModelClass(event.model)),\n ...this.viewDialogOptions\n });\n } else {\n // Fallback to data view\n await Dialog.showData({\n title: `View ${this.getModelName(event.model)} #${event.model.id}`,\n model: event.model\n });\n }\n }\n\n /**\n * Handle row edit action\n */\n async _onRowEdit(event) {\n this.emit('row:edit', event);\n\n // Check for custom handler first\n if (this.options.onItemEdit) {\n await this.options.onItemEdit(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n let formConfig = this.getEditFormConfig(ModelClass);\n\n if (formConfig) {\n if (!formConfig.fields) {\n formConfig = { title: `Edit ${this.getModelName(event.model)}`, fields: formConfig };\n }\n\n const result = await Dialog.showModelForm({\n model: event.model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (!result) return; // Cancelled\n\n if (!result.success || !result?.result?.data.status) {\n Dialog.showError(result?.result?.data?.error || result?.result?.message || \"An error occurred\");\n return;\n }\n\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const result = await Dialog.showDialog({\n title: `Edit ${this.getModelName(event.model)} #${event.model.id}`,\n body: new FormView({\n model: event.model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await event.model.save(result);\n if (!resp.data?.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle row delete action\n */\n async _onRowDelete(event) {\n this.emit('row:delete', event);\n\n // Check for custom handler first\n if (this.options.onItemDelete) {\n await this.options.onItemDelete(event.model, event.event);\n return;\n }\n\n const ModelClass = this.getModelClass(event.model);\n\n // Get delete template from options, Model class, or use default\n const template = this.deleteTemplate ||\n ModelClass?.DELETE_TEMPLATE ||\n 'Are you sure you want to delete this {{name||\"item\"}}?';\n\n // Render template with model context\n const message = this.renderTemplateString(template, event.model);\n\n const confirmed = await Dialog.confirm({\n message: message || 'Are you sure you want to delete this item?',\n title: 'Confirm Delete',\n confirmText: 'Delete',\n confirmClass: 'btn-danger'\n });\n\n if (confirmed) {\n await event.model.destroy();\n this.collection.fetch();\n }\n }\n\n /**\n * Handle cell edit event\n */\n _onCellEdit(event) {\n this.emit('cell:edit', event);\n }\n\n /**\n * Handle cell save event\n */\n async _onCellSave(event) {\n this.emit('cell:save', event);\n // Model save is now handled directly in TableRow.saveCellEdit()\n }\n\n /**\n * Handle cell cancel event\n */\n _onCellCancel(event) {\n this.emit('cell:cancel', event);\n }\n\n /**\n * Check if fullscreen is supported by the browser\n */\n isFullscreenSupported() {\n return !!(\n document.fullscreenEnabled ||\n document.mozFullScreenEnabled ||\n document.webkitFullscreenEnabled ||\n document.msFullscreenEnabled\n );\n }\n\n /**\n * Handle toggle fullscreen action\n */\n async onActionToggleFullscreen(event, element) {\n if (this.isFullscreen) {\n await this.exitFullscreen();\n } else {\n await this.enterFullscreen();\n }\n }\n\n /**\n * Enter fullscreen mode\n */\n async enterFullscreen() {\n try {\n // Use browser's native fullscreen API\n if (this.element.requestFullscreen) {\n await this.element.requestFullscreen();\n } else if (this.element.mozRequestFullScreen) {\n await this.element.mozRequestFullScreen();\n } else if (this.element.webkitRequestFullscreen) {\n await this.element.webkitRequestFullscreen();\n } else if (this.element.msRequestFullscreen) {\n await this.element.msRequestFullscreen();\n }\n\n this.isFullscreen = true;\n this.element.classList.add('table-fullscreen');\n this.updateFullscreenButton();\n\n // Listen for fullscreen change events\n this.setupFullscreenListeners();\n\n this.emit('table:fullscreen:enter');\n\n } catch (error) {\n console.warn('Could not enter fullscreen:', error);\n }\n }\n\n /**\n * Exit fullscreen mode\n */\n async exitFullscreen() {\n try {\n if (document.exitFullscreen) {\n await document.exitFullscreen();\n } else if (document.mozCancelFullScreen) {\n await document.mozCancelFullScreen();\n } else if (document.webkitExitFullscreen) {\n await document.webkitExitFullscreen();\n } else if (document.msExitFullscreen) {\n await document.msExitFullscreen();\n }\n\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n\n this.emit('table:fullscreen:exit');\n\n } catch (error) {\n console.warn('Could not exit fullscreen:', error);\n }\n }\n\n /**\n * Update fullscreen button icon and title\n */\n updateFullscreenButton() {\n const button = this.element?.querySelector('.btn-fullscreen');\n const icon = button?.querySelector('i');\n\n if (button && icon) {\n if (this.isFullscreen) {\n icon.className = 'bi bi-fullscreen-exit';\n button.title = 'Exit Fullscreen';\n } else {\n icon.className = 'bi bi-fullscreen';\n button.title = 'Enter Fullscreen';\n }\n }\n }\n\n /**\n * Setup fullscreen event listeners\n */\n setupFullscreenListeners() {\n // Don't add listeners multiple times\n if (this._fullscreenHandler) return;\n\n const handleFullscreenChange = () => {\n const isCurrentlyFullscreen = !!(\n document.fullscreenElement ||\n document.mozFullScreenElement ||\n document.webkitFullscreenElement ||\n document.msFullscreenElement\n );\n\n if (!isCurrentlyFullscreen && this.isFullscreen) {\n // User exited fullscreen via ESC or browser controls\n this.isFullscreen = false;\n this.element.classList.remove('table-fullscreen');\n this.updateFullscreenButton();\n this.emit('table:fullscreen:exit');\n }\n };\n\n // Add listeners for all browser prefixes\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n document.addEventListener('msfullscreenchange', handleFullscreenChange);\n\n // Store handler for cleanup\n this._fullscreenHandler = handleFullscreenChange;\n }\n\n /**\n * Cleanup fullscreen listeners\n */\n cleanupFullscreenListeners() {\n if (this._fullscreenHandler) {\n document.removeEventListener('fullscreenchange', this._fullscreenHandler);\n document.removeEventListener('mozfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('webkitfullscreenchange', this._fullscreenHandler);\n document.removeEventListener('msfullscreenchange', this._fullscreenHandler);\n this._fullscreenHandler = null;\n }\n }\n\n /**\n * Override destroy to cleanup fullscreen listeners\n */\n destroy() {\n this.cleanupFullscreenListeners();\n super.destroy();\n }\n\n /**\n * Handle refresh action\n */\n async onActionRefresh(event, element) {\n await this.refresh();\n }\n\n /**\n * Handle add action\n */\n async onActionAdd(event, element) {\n // Check for custom handler first - if provided, just emit event and let handler deal with it\n if (this.options.onAdd) {\n this.emit('table:add', { event });\n await this.options.onAdd(event);\n return;\n }\n\n // Emit event for external listeners\n this.emit('table:add', { event });\n\n const ModelClass = this.getModelClass();\n if (!ModelClass) {\n console.warn('Cannot determine Model class for add operation');\n return;\n }\n\n let formConfig = this.getAddFormConfig(ModelClass);\n\n if (formConfig) {\n const model = new ModelClass();\n if (!formConfig.fields) {\n formConfig = { title: `Add ${this.getModelName()}`, fields: formConfig };\n }\n\n const result = await Dialog.showForm({\n model: model,\n ...formConfig,\n ...this.getFormDialogConfig(ModelClass)\n });\n\n if (result) {\n if (this.options.addRequiresActiveGroup) {\n result.group = this.getApp().activeGroup.id;\n }\n if (this.options.addRequiresActiveUser) {\n result.user = this.getApp().activeUser.id;\n }\n if (this.options.addFormDefaults) {\n Object.assign(result, this.options.addFormDefaults);\n }\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp?.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n } else {\n // Fallback to basic form if no config provided\n // Using statically imported FormView\n const model = new ModelClass();\n\n const result = await Dialog.showDialog({\n title: `Add ${this.getModelName()}`,\n body: new FormView({\n model: model,\n fields: this.options.formFields || []\n })\n });\n\n if (result) {\n const resp = await model.save(result);\n if (!resp?.data.status) {\n Dialog.showError(resp.data.error || 'An error occurred');\n return;\n }\n if (this.collection) {\n this.collection.add(model);\n }\n await this.refresh();\n }\n }\n }\n\n /**\n * Handle export action\n */\n async onActionExport(event, element) {\n const format = element.getAttribute('data-format') || 'json';\n\n this.emit('table:export', {\n format: format,\n source: this.exportSource,\n event\n });\n\n if (this.exportSource === 'remote') {\n if (this.collection) {\n await this.collection.download(format);\n } else {\n console.warn('TableView: Cannot export from remote without a collection.');\n }\n } else {\n // Handle local export (future enhancement)\n if (this.options.onExport) {\n await this.options.onExport(this.collection?.toJSON() || [], format);\n } else {\n console.warn('TableView: onExport handler not implemented for local export.');\n }\n }\n }\n\n /**\n * Handle search action (Enter key triggers this via EventDelegate)\n */\n async onActionApplySearch(event, element) {\n const searchTerm = element.value.trim();\n\n if (this.collection) {\n this.setFilter('search', searchTerm);\n\n // Reset to first page when searching\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side filtering\n this.render();\n }\n }\n\n // Update filter pills when search changes\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear search button\n */\n async onActionClearSearch(event, element) {\n // Clear the search filter\n this.setFilter('search', null);\n\n // Reset to first page\n if (this.collection) {\n this.collection.params.start = 0;\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n }\n\n // Render will rebuild the search input with empty value\n await this.render();\n this.updateFilterPills();\n\n this.emit('table:search', { searchTerm: '', event });\n this.emit('params-changed');\n }\n\n /**\n * Get current sort field\n */\n getSortBy() {\n const sort = this.collection?.params?.sort;\n if (!sort) return null;\n return sort.startsWith('-') ? sort.slice(1) : sort;\n }\n\n /**\n * Get current sort direction\n */\n getSortDirection() {\n const sort = this.collection?.params?.sort;\n if (!sort) return 'asc';\n return sort.startsWith('-') ? 'desc' : 'asc';\n }\n\n /**\n * Get sort icon based on current sort direction\n */\n getSortIcon(direction) {\n if (direction === 'asc') {\n return '<i class=\"bi bi-sort-alpha-down text-primary\"></i>';\n } else if (direction === 'desc') {\n return '<i class=\"bi bi-sort-alpha-down-alt text-primary\"></i>';\n } else {\n return '<i class=\"bi bi-three-dots-vertical text-muted\"></i>';\n }\n }\n\n /**\n * Handle sort action\n */\n async onActionSort(event, element) {\n event.preventDefault();\n const field = element.getAttribute('data-field');\n const direction = element.getAttribute('data-direction');\n\n if (this.collection) {\n let newSort;\n\n if (direction === 'none') {\n newSort = undefined; // Remove sort\n } else if (direction === 'desc') {\n newSort = `-${field}`; // Descending sort\n } else {\n newSort = field; // Ascending sort\n }\n\n this.collection.setParams({\n ...this.collection.params,\n sort: newSort,\n start: 0 // Reset to first page when sorting changes\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n // Client-side sorting\n if (newSort) {\n const desc = newSort.startsWith('-');\n const sortField = desc ? newSort.slice(1) : newSort;\n\n this.collection.sort((a, b) => {\n const aVal = a.get(sortField);\n const bVal = b.get(sortField);\n\n if (aVal < bVal) return desc ? 1 : -1;\n if (aVal > bVal) return desc ? -1 : 1;\n return 0;\n });\n }\n\n this.render();\n }\n }\n\n // Update sort icons in the DOM\n this.updateSortIcons();\n\n this.emit('table:sort', { field, event });\n this.emit('params-changed');\n }\n\n /**\n * Update sort icons in all column headers\n */\n updateSortIcons() {\n if (!this.element) return;\n\n const currentSortField = this.getSortBy();\n const currentSortDir = this.getSortDirection();\n\n // Update all sort dropdown buttons\n this.columns.forEach(column => {\n if (this.sortable && column.sortable !== false) {\n // Parse the column key to get just the field name (without pipes/formatters)\n const { fieldKey } = this.parseColumnKey(column.key);\n\n const dropdown = this.element.querySelector(`[data-bs-toggle=\"dropdown\"][data-column=\"${fieldKey}\"]`);\n if (dropdown) {\n const isSorted = currentSortField === fieldKey;\n const sortIcon = this.getSortIcon(isSorted ? currentSortDir : null);\n dropdown.innerHTML = sortIcon;\n\n // Update dropdown menu items\n const dropdownMenu = dropdown.nextElementSibling;\n if (dropdownMenu) {\n const ascItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"asc\"]`);\n const descItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"desc\"]`);\n const noneItem = dropdownMenu.querySelector(`[data-field=\"${fieldKey}\"][data-direction=\"none\"]`);\n\n if (ascItem) {\n ascItem.classList.toggle('active', isSorted && currentSortDir === 'asc');\n }\n if (descItem) {\n descItem.classList.toggle('active', isSorted && currentSortDir === 'desc');\n }\n if (noneItem) {\n noneItem.classList.toggle('active', !isSorted || currentSortField !== fieldKey);\n }\n }\n }\n }\n });\n }\n\n /**\n * Handle select all action\n */\n async onActionSelectAll(event, element) {\n event.stopPropagation();\n const isCurrentlyAllSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n\n if (!isCurrentlyAllSelected) {\n // Select all visible items\n this.forEachItem(itemView => {\n if (!itemView.selected) {\n itemView.select();\n }\n });\n } else {\n // Deselect all\n this.clearSelection();\n }\n\n // Update select all checkbox visual state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n selectAllCell.classList.toggle('selected', !isCurrentlyAllSelected);\n }\n\n // Update batch actions panel\n this.updateBatchActionsPanel();\n }\n\n /**\n * Override onBeforeRender to set data properties before rendering\n */\n async onBeforeRender() {\n // Set properties that Mustache needs\n this.searchValue = this.getActiveFilters().search || '';\n this.footerTotals = this.calculateFooterTotals();\n }\n\n /**\n * Override onAfterRender to update pagination info\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Update footer totals in case collection loaded after initial render\n if (this.hasFooterTotals) {\n this.updateFooterTotals();\n }\n\n // Update pagination info\n if (this.paginated && this.collection) {\n const total = this.collection.meta?.count || this.collection.length();\n const start = this.collection.params?.start || 0;\n const size = this.collection.params?.size || 10;\n const end = Math.min(start + size, total);\n\n const startEl = this.element.querySelector('[data-value=\"start\"]');\n const endEl = this.element.querySelector('[data-value=\"end\"]');\n const totalEl = this.element.querySelector('[data-value=\"total\"]');\n\n if (startEl) startEl.textContent = start + 1;\n if (endEl) endEl.textContent = end;\n if (totalEl) totalEl.textContent = total;\n\n // Update page size selector\n const pageSizeSelect = this.element.querySelector('[data-change-action=\"page-size\"]');\n if (pageSizeSelect) {\n pageSizeSelect.value = size;\n }\n\n // Render pagination controls\n this.renderPagination();\n }\n\n // Update sort icons after render\n this.updateSortIcons();\n\n // Update filter pills after render - this is crucial for showing pills on page load\n this.updateFilterPills();\n\n // Re-setup search clear listener after render\n this.setupSearchClearListener();\n }\n\n /**\n * Render pagination controls\n * - Prev/Next wrap around (never disabled)\n * - Truncated page list with first/last and ellipses\n */\n renderPagination() {\n const paginationContainer = this.element.querySelector('[data-container=\"pagination\"]');\n if (!paginationContainer || !this.collection) return;\n\n const total = this.collection.meta?.count || this.collection.length();\n const size = this.collection.params?.size || 10;\n const start = this.collection.params?.start || 0;\n const currentPage = Math.floor(start / size) + 1;\n const totalPages = Math.ceil(total / size);\n\n if (totalPages <= 1) {\n paginationContainer.innerHTML = '';\n return;\n }\n\n const prevPage = currentPage > 1 ? currentPage - 1 : totalPages;\n const nextPage = currentPage < totalPages ? currentPage + 1 : 1;\n\n const pages = [];\n\n // Previous (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${prevPage}\">\n <i class=\"bi bi-chevron-left\"></i>\n </a>\n </li>\n `);\n\n // Build truncated page list: always show 1 and totalPages, with neighbors around current\n const neighbors = 1; // how many pages to show on each side of current\n const visibleSet = new Set([1, totalPages]);\n for (let i = currentPage - neighbors; i <= currentPage + neighbors; i++) {\n if (i >= 1 && i <= totalPages) visibleSet.add(i);\n }\n const visible = Array.from(visibleSet).sort((a, b) => a - b);\n\n // Render pages with ellipses where there are gaps\n let last = 0;\n for (const p of visible) {\n if (last && p - last > 1) {\n // gap -> ellipsis\n pages.push(`\n <li class=\"page-item disabled\"><span class=\"page-link\">…</span></li>\n `);\n }\n pages.push(`\n <li class=\"page-item ${p === currentPage ? 'active' : ''}\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${p}\">${p}</a>\n </li>\n `);\n last = p;\n }\n\n // Next (wraps)\n pages.push(`\n <li class=\"page-item\">\n <a class=\"page-link\" href=\"#\" data-action=\"page\" data-page=\"${nextPage}\">\n <i class=\"bi bi-chevron-right\"></i>\n </a>\n </li>\n `);\n\n paginationContainer.innerHTML = pages.join('');\n }\n\n /**\n * Handle page change\n * - Normalizes and wraps page number (1..totalPages)\n */\n async onActionPage(event, element) {\n event.preventDefault();\n\n const rawPage = parseInt(element.getAttribute('data-page'), 10);\n const size = this.collection.params?.size || 10;\n const total = this.collection.meta?.count || this.collection.length();\n const totalPages = Math.max(1, Math.ceil(total / size));\n\n let page = isNaN(rawPage) ? 1 : rawPage;\n if (page < 1) page = totalPages;\n if (page > totalPages) page = 1;\n\n this.collection.setParams({\n ...this.collection.params,\n start: (page - 1) * size\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n } else {\n this.render();\n }\n\n this.emit('table:page', { page, event });\n this.emit('params-changed');\n }\n\n /**\n * Handle page size change\n */\n async onChangePageSize(event, element) {\n const newSize = parseInt(element.value);\n\n if (this.collection) {\n // Reset to first page when changing page size\n this.collection.setParams({\n ...this.collection.params,\n start: 0,\n size: newSize\n });\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n }\n\n this.emit('table:pagesize', { size: newSize, event });\n this.emit('params-changed');\n }\n\n /**\n * Get active filters from collection params\n */\n getActiveFilters() {\n if (!this.collection?.params) {\n return {};\n }\n const { start, size, sort, ...allParams } = this.collection.params;\n const filters = {};\n\n // Reconstruct daterange filters from their component parts\n const processedKeys = new Set();\n\n // First pass: identify and process daterange filters\n const allFilterConfigs = this.getAllAvailableFilters();\n allFilterConfigs.forEach(filterDef => {\n if (filterDef.config.type === 'daterange') {\n const key = filterDef.key;\n const startName = filterDef.config.startName || 'dr_start';\n const endName = filterDef.config.endName || 'dr_end';\n const fieldName = filterDef.config.fieldName || 'dr_field';\n\n // Check if this daterange filter is active for this specific key\n if (allParams[fieldName] === key && (allParams[startName] || allParams[endName])) {\n filters[key] = {\n start: allParams[startName] || '',\n end: allParams[endName] || ''\n };\n\n processedKeys.add(startName);\n processedKeys.add(endName);\n processedKeys.add(fieldName);\n }\n }\n });\n\n // Second pass: add remaining filters\n Object.keys(allParams).forEach(paramKey => {\n if (!processedKeys.has(paramKey)) {\n filters[paramKey] = allParams[paramKey];\n }\n });\n\n return filters;\n }\n\n /**\n * Set a filter value\n */\n setFilter(key, value) {\n if (!this.collection) return;\n\n const filterConfig = this.getFilterConfig(key);\n\n // Handle daterange filters specially\n if (filterConfig && filterConfig.type === 'daterange') {\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n const fieldName = filterConfig.fieldName || 'dr_field';\n\n // Always remove old values first\n delete this.collection.params[startName];\n delete this.collection.params[endName];\n delete this.collection.params[fieldName];\n\n // Set new values if provided and not empty\n if (value && typeof value === 'object' && (value.start || value.end)) {\n if (value.start) this.collection.params[startName] = value.start;\n if (value.end) this.collection.params[endName] = value.end;\n this.collection.params[fieldName] = key;\n }\n } else {\n // Parse key to get field and lookup\n const { field, lookup } = parseFilterKey(key);\n \n // Clear old values - remove both base field and variants\n delete this.collection.params[key];\n delete this.collection.params[field];\n delete this.collection.params[`${field}__in`];\n \n if (!value || (Array.isArray(value) && value.length === 0)) {\n return; // Cleared\n }\n \n // Smart param generation for multiselect fields\n if (Array.isArray(value)) {\n if (value.length === 1) {\n // Single value from array - use simple key (no __in)\n this.collection.params[field] = value[0];\n } else {\n // Multiple values - use __in lookup\n this.collection.params[`${field}__in`] = value.join(',');\n }\n } else {\n // Single value - use key as-is (may include lookup like __gte)\n this.collection.params[key] = value;\n }\n }\n }\n\n /**\n * Get all available filters\n */\n getAllAvailableFilters() {\n const filters = [];\n\n // Add column-based filters\n this.columns.forEach(column => {\n if (column.filter) {\n const { fieldKey } = this.parseColumnKey(column.key);\n filters.push({\n key: fieldKey,\n label: column.filter.label || column.label || fieldKey,\n type: column.filter.type,\n config: column.filter\n });\n }\n });\n\n // Add additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n this.additionalFilters.forEach(filter => {\n filters.push({\n key: filter.name || filter.key,\n label: filter.label,\n type: filter.type,\n config: filter\n });\n });\n }\n\n return filters;\n }\n\n /**\n * Get filter configuration for a key\n */\n getFilterConfig(filterKey) {\n // Check column filters first\n const column = this.columns.find(col => {\n const { fieldKey } = this.parseColumnKey(col.key);\n return fieldKey === filterKey;\n });\n if (column && column.filter) {\n return column.filter;\n }\n\n // Check additional filters\n if (this.additionalFilters && Array.isArray(this.additionalFilters)) {\n const filter = this.additionalFilters.find(f => (f.name || f.key) === filterKey);\n if (filter) {\n return filter;\n }\n }\n\n return null;\n }\n\n /**\n * Get filter label\n */\n getFilterLabel(key) {\n if (key === 'search') return 'Search';\n\n const filter = this.filters[key];\n if (filter && filter.label) return filter.label;\n\n const additionalFilter = this.additionalFilters.find(f =>\n (f.name || f.key) === key\n );\n if (additionalFilter && additionalFilter.label) return additionalFilter.label;\n\n return key.charAt(0).toUpperCase() + key.slice(1);\n }\n\n /**\n * Get filter display value\n */\n getFilterDisplayValue(key, value) {\n if (key === 'search') return `\"${value}\"`;\n\n const filter = this.filters[key] ||\n this.additionalFilters.find(f => (f.name || f.key) === key);\n\n if (filter && filter.type === 'daterange' && typeof value === 'object') {\n const start = value.start || '';\n const end = value.end || '';\n return `${start} to ${end}`;\n }\n\n if (filter && filter.type === 'select' && filter.options) {\n if (typeof filter.options[0] === 'object') {\n const option = filter.options.find(opt => opt.value === value);\n return option ? option.label : value;\n }\n return value;\n }\n\n return value;\n }\n\n /**\n * Get icon for filter type\n */\n getFilterIcon(type) {\n const icons = {\n 'text': 'search',\n 'select': 'funnel',\n 'date': 'calendar',\n 'daterange': 'calendar-range',\n 'number': '123',\n 'boolean': 'toggle-on'\n };\n return icons[type] || 'filter';\n }\n\n /**\n * Handle add filter action\n */\n async onActionAddFilter(event, element) {\n const filterKey = element.getAttribute('data-filter-key');\n const filterConfig = this.getFilterConfig(filterKey);\n const currentValue = this.getActiveFilters()[filterKey];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey);\n return;\n }\n\n // Using statically imported Dialog\n\n // Show dialog for this specific filter\n const result = await Dialog.showForm({\n title: `${currentValue !== undefined && currentValue !== '' ? 'Edit' : 'Add'} ${this.getFilterLabel(filterKey)} Filter`,\n size: 'md',\n fields: [this.buildFilterDialogField(filterConfig, currentValue, filterKey)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n // Use the filter key for setFilter (it will handle the lookup internally)\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Build filter dialog field configuration\n */\n buildFilterDialogField(filterConfig, currentValue, filterKey) {\n const field = {\n name: 'filter_value',\n label: filterConfig.label,\n value: currentValue,\n ...filterConfig,\n // Ensure placeholder is passed through (support both casings)\n placeholder: filterConfig.placeholder || filterConfig.placeHolder\n };\n\n // Set current value appropriately based on filter type\n if (filterConfig.type === 'daterange') {\n // Apply defaults for daterange\n field.startName = field.startName || 'dr_start';\n field.endName = field.endName || 'dr_end';\n field.fieldName = field.fieldName || 'dr_field';\n field.format = field.format || 'YYYY-MM-DD';\n field.displayFormat = field.displayFormat || 'MMM DD, YYYY';\n field.separator = field.separator || ' to ';\n field.label = field.label || 'Date Range';\n\n // Handle daterange current values\n if (currentValue && typeof currentValue === 'object') {\n field.startDate = currentValue.start || '';\n field.endDate = currentValue.end || '';\n }\n } else if (filterConfig.type === 'multiselect') {\n // Convert comma-separated string to array for multiselect\n let valueArray = [];\n if (currentValue) {\n if (Array.isArray(currentValue)) {\n valueArray = currentValue;\n } else if (typeof currentValue === 'string') {\n // Split by comma and trim whitespace\n valueArray = currentValue.split(',').map(v => v.trim()).filter(v => v);\n }\n }\n \n field.value = valueArray;\n \n // Ensure placeholder is set (support both casings)\n if (!field.placeholder && !field.placeHolder) {\n if (filterConfig.placeholder || filterConfig.placeHolder) {\n field.placeholder = filterConfig.placeholder || filterConfig.placeHolder;\n } else if (filterConfig.label) {\n field.placeholder = `Select ${filterConfig.label}...`;\n }\n }\n }\n\n return field;\n }\n\n /**\n * Extract filter value from form result\n */\n extractFilterValue(filterConfig, formResult) {\n if (filterConfig.type === 'daterange') {\n // Extract start/end values based on naming convention\n const startName = filterConfig.startName || 'dr_start';\n const endName = filterConfig.endName || 'dr_end';\n\n const result = {\n start: formResult[startName],\n end: formResult[endName]\n };\n\n return result;\n }\n\n if (filterConfig.type === 'multiselect') {\n // Return array as-is for multiselect\n return formResult.filter_value;\n }\n\n return formResult.filter_value;\n }\n\n /**\n * Apply filters to collection and refresh\n */\n async applyFilters() {\n // Reset to first page when filters change\n if (this.collection) {\n this.collection.params.start = 0;\n }\n\n // For REST collections, fetch data with new filters\n if (this.collection?.restEnabled) {\n try {\n await this.collection.fetch();\n this.render();\n } catch (error) {\n console.error('Failed to fetch filtered data:', error);\n this.render();\n }\n } else {\n this.render();\n }\n\n // Update filter pills display\n this.updateFilterPills();\n\n // Emit params changed event for URL synchronization\n this.emit('params-changed');\n }\n\n /**\n * Handle edit filter action from pill\n */\n async onActionEditFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n \n // Parse the key to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n \n // Try to get filter config using the parsed field name first, then original key\n let filterConfig = this.getFilterConfig(field) || this.getFilterConfig(filterKey);\n \n // Get current value - could be under filterKey or field\n const activeFilters = this.getActiveFilters();\n const currentValue = activeFilters[filterKey] || activeFilters[field];\n\n if (!filterConfig) {\n console.warn('No filter config found for key:', filterKey, 'or field:', field);\n return;\n }\n\n // Using statically imported Dialog\n\n // Show mini dialog for this specific filter\n const result = await Dialog.showForm({\n title: `Edit ${this.getFilterLabel(field)} Filter`,\n size: 'md',\n data: {filter_value: currentValue},\n fields: [this.buildFilterDialogField(filterConfig, currentValue, field)]\n });\n\n if (result) {\n // Extract the new filter value\n const newFilterValue = this.extractFilterValue(filterConfig, result);\n this.setFilter(filterKey, newFilterValue);\n await this.applyFilters();\n }\n }\n\n /**\n * Handle remove filter action\n */\n async onActionRemoveFilter(event, element) {\n const filterKey = element.getAttribute('data-filter');\n \n // Parse to get the base field (handles lookup keys like status__in)\n const { field } = parseFilterKey(filterKey);\n \n // Clear the filter using the original key\n this.setFilter(filterKey, null);\n\n // If removing search filter, clear search inputs\n if (filterKey === 'search') {\n this.updateSearchInputs('');\n }\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after removing\n this.updateFilterPills();\n\n this.emit('filter:remove', { key: filterKey, field });\n this.emit('params-changed');\n }\n\n /**\n * Handle clear all filters action\n */\n async onActionClearAllFilters(event, element) {\n if (!this.collection) return;\n\n // Clear all filters except pagination and sorting\n const { start, size, sort } = this.collection.params;\n this.collection.params = { start, size };\n if (sort) this.collection.params.sort = sort;\n\n // Clear all search inputs\n this.updateSearchInputs('');\n\n if (this.collection.restEnabled) {\n await this.collection.fetch();\n }\n this.render();\n\n // Update filter pills after clearing\n this.updateFilterPills();\n\n this.emit('filters:clear');\n this.emit('params-changed');\n }\n\n /**\n * Update batch actions panel visibility and count\n */\n updateBatchActionsPanel() {\n if (!this.batchActions || this.batchActions.length === 0) return;\n\n const selectedCount = this.getSelectedItems().length;\n\n if (this.batchBarLocation === 'top') {\n // Handle top panel style\n const panel = this.element?.querySelector('.batch-actions-panel-top');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n\n // Use Bootstrap's d-none class for cleaner show/hide\n if (selectedCount > 0) {\n panel.classList.remove('d-none');\n } else {\n panel.classList.add('d-none');\n }\n }\n } else {\n // Handle bottom panel style (original)\n const panel = this.element?.querySelector('.batch-actions-panel');\n const countEl = this.element?.querySelector('.batch-select-count');\n\n if (panel && countEl) {\n countEl.textContent = selectedCount;\n panel.style.display = selectedCount > 0 ? 'block' : 'none';\n }\n }\n\n // Update select all checkbox state\n const selectAllCell = this.element?.querySelector('.mojo-select-all-cell');\n if (selectAllCell) {\n const allSelected = this.itemViews.size > 0 &&\n Array.from(this.itemViews.values()).every(item => item.selected);\n const someSelected = Array.from(this.itemViews.values()).some(item => item.selected);\n\n selectAllCell.classList.toggle('selected', allSelected);\n selectAllCell.classList.toggle('indeterminate', !allSelected && someSelected);\n\n // Update icon for indeterminate state\n const icon = selectAllCell.querySelector('i');\n if (icon) {\n icon.className = !allSelected && someSelected ? 'bi bi-dash' : 'bi bi-check';\n }\n }\n }\n\n /**\n * Handle batch action clicks\n */\n async onActionBatch(event, element) {\n const batchAction = element.getAttribute('data-action').replace('batch-', '');\n const selectedItems = this.getSelectedItems();\n\n this.emit('batch:action', {\n action: batchAction,\n items: selectedItems,\n event\n });\n }\n\n /**\n * Handle clear selection action (for top batch bar)\n */\n async onActionClearSelection(event, element) {\n this.clearSelection();\n this.updateBatchActionsPanel();\n }\n\n /**\n * Handle custom toolbar button with handler function\n */\n async onActionCustomToolbarButton(event, element) {\n const buttonIndex = parseInt(element.getAttribute('data-button-index'), 10);\n const button = this.toolbarButtons[buttonIndex];\n\n if (button && typeof button.handler === 'function') {\n await button.handler.call(this, event, element);\n }\n }\n}\n\nexport default TableView;\n","/**\n * TablePage - Page component that manages a TableView with URL parameter synchronization\n *\n * A clean, simplified implementation using the new TableView component.\n * Automatically syncs pagination, sorting, and filtering with URL parameters.\n *\n * @example\n * const usersPage = new TablePage({\n * pageName: 'users',\n * title: 'User Management',\n * collection: userCollection,\n * columns: [\n * { key: 'name', label: 'Name', sortable: true },\n * { key: 'email', label: 'Email' },\n * { key: 'role', label: 'Role', type: 'badge' }\n * ],\n * actions: ['view', 'edit', 'delete']\n * });\n */\n\nimport Page from '@core/Page.js';\nimport Dialog from '@core/views/feedback/Dialog.js';\nimport TableView from '@core/views/table/TableView.js';\nimport Collection from '@core/Collection.js';\n\nclass TablePage extends Page {\n constructor(options = {}) {\n super({\n ...options,\n pageName: options.pageName || options.name || 'table'\n });\n\n // Page configuration\n this.title = options.title || this.pageName;\n this.description = options.description || '';\n\n // Collection setup\n this.Collection = options.Collection || null;\n this.collection = options.collection || null;\n\n // our default collection query\n this.defaultQuery = options.defaultQuery || {};\n\n // Group field configuration - defaults to \"group\"\n this.groupField = options.groupField || 'group';\n\n // Store configuration for TableView\n // Map legacy property names to new ones\n this.tableViewConfig = {\n // Core table properties\n columns: options.columns || [],\n actions: options.actions || null,\n contextMenu: options.contextMenu || null,\n batchActions: options.batchActions || null,\n batchBarLocation: options.batchBarLocation || 'top',\n clickAction: options.clickAction || 'view',\n // Map legacy form properties to new names\n addForm: options.addForm || options.formFields || options.formCreate,\n editForm: options.editForm || options.formEdit || options.formFields,\n\n // Model operation configurations\n itemView: options.itemView,\n deleteTemplate: options.deleteTemplate,\n formDialogConfig: options.formDialogConfig,\n viewDialogOptions: options.viewDialogOptions,\n\n // Features\n searchable: options.searchable !== false,\n sortable: options.sortable !== false,\n filterable: options.filterable !== false,\n paginated: options.paginated !== false,\n\n // Selection mode\n selectionMode: options.selectionMode || (options.selectable ? 'multiple' : 'none'),\n\n // Filter configuration\n filters: options.filters || options.additionalFilters || [],\n hideActivePills: options.hideActivePills || false,\n hideActivePillNames: options.hideActivePillNames || [],\n searchPlacement: options.searchPlacement || 'toolbar',\n\n // Display options for the HTML table element\n tableOptions: {\n striped: true,\n bordered: false,\n hover: true,\n responsive: false,\n ...options.tableOptions\n },\n\n // Additional options\n emptyMessage: options.emptyMessage || 'No data available',\n searchPlaceholder: options.searchPlaceholder || 'Search...',\n showAdd: options.showAdd !== false,\n showExport: options.showExport !== false,\n\n // Custom handlers\n onItemView: options.onItemView,\n onItemEdit: options.onItemEdit,\n onItemDelete: options.onItemDelete,\n onAdd: options.onAdd,\n onExport: options.onExport,\n\n // Override with tableViewOptions if provided\n ...options.tableViewOptions\n };\n\n // URL synchronization\n this.urlSyncEnabled = options.urlSyncEnabled !== false;\n\n // Status tracking\n this.lastUpdated = null;\n this.isLoading = false;\n\n // Set up template\n this.template = options.template || this.buildTemplate();\n }\n\n /**\n * Build the page template\n */\n buildTemplate() {\n return `\n <div class=\"table-page-container\">\n\n <div class=\"table-container\" data-container=\"table\"></div>\n\n {{#showStatus}}\n <div class=\"table-status-bar table-status-top\">\n <div class=\"status-info\">\n <div class=\"d-flex justify-content-between w-100\">\n <span class=\"text-muted\">\n <i class=\"bi bi-clock me-1\"></i>\n Last updated: <span data-status=\"last-updated\">{{lastUpdated}}</span>\n </span>\n <span class=\"text-muted\">\n <i class=\"bi bi-list-ol me-1\"></i>\n Total records: <span data-status=\"record-count\">0</span>\n </span>\n </div>\n </div>\n </div>\n {{/showStatus}}\n\n </div>\n `;\n }\n\n /**\n * Initialize the page\n */\n async onInit() {\n await super.onInit();\n\n // Create collection if needed\n if (!this.collection) {\n if (this.Collection) {\n this.collection = new this.Collection();\n } else {\n this.collection = new Collection();\n }\n }\n\n // Apply URL query parameters to collection\n this.applyQueryToCollection();\n\n // Create TableView instance with all configuration\n this.tableView = new TableView({\n collection: this.collection,\n containerId: 'table',\n fetchOnMount: true,\n ...this.tableViewConfig\n });\n\n // Add as child view\n this.addChild(this.tableView);\n\n // Set up event listeners\n this.setupEventListeners();\n }\n\n /**\n * Set up event listeners\n */\n setupEventListeners() {\n // Listen for collection changes to sync URL\n if (this.urlSyncEnabled && this.collection) {\n // Sync URL when collection params change\n this.collection.on('fetch:start', () => {\n this.isLoading = true;\n });\n\n this.collection.on('fetch:end', () => {\n this.isLoading = false;\n this.lastUpdated = new Date().toLocaleTimeString();\n this.updateStatusDisplay();\n });\n }\n\n // Listen for params-changed event from TableView to sync URL\n this.tableView.on('params-changed', () => {\n if (this.urlSyncEnabled) {\n this.syncUrl();\n }\n });\n\n // // Listen for table events (these also emit params-changed, but keep for backwards compatibility)\n // this.tableView.on('table:search', ({ searchTerm }) => {\n // // params-changed will handle URL sync\n // });\n\n // this.tableView.on('table:sort', ({ field }) => {\n // // params-changed will handle URL sync\n // });\n\n // this.tableView.on('table:page', ({ page }) => {\n // // params-changed will handle URL sync\n // });\n\n // Filter events - params-changed will handle URL sync\n this.tableView.on('filter:edit', async ({ key }) => {\n await this.handleFilterEdit(key);\n });\n\n // Row action events\n this.tableView.on('row:view', async ({ model }) => {\n if (this.onItemView) {\n await this.onItemView(model);\n }\n });\n\n this.tableView.on('row:edit', async ({ model }) => {\n if (this.onItemEdit) {\n await this.onItemEdit(model);\n }\n });\n\n this.tableView.on('row:delete', async ({ model }) => {\n if (this.onItemDelete) {\n await this.onItemDelete(model);\n }\n });\n\n // Table action events\n // Note: TableView will call options.onAdd if provided, but we still listen to the event\n // for backwards compatibility and to support event-based patterns\n this.tableView.on('table:add', async ({ event }) => {\n // The handler was already called by TableView if options.onAdd is set,\n // but we keep this listener for external code that might listen to 'table:add'\n // We don't call this.tableViewConfig.onAdd here to avoid double execution\n });\n\n this.tableView.on('table:export', async ({ data }) => {\n if (this.tableViewConfig.onExport) {\n await this.tableViewConfig.onExport(data);\n }\n });\n }\n\n /**\n * Apply URL query parameters to collection\n */\n applyQueryToCollection() {\n const params = {};\n const query = { ...this.defaultQuery, ...this.query };\n if (!query || Object.keys(query).length === 0) {\n return;\n }\n // Pagination\n if (query.start !== undefined) params.start = parseInt(query.start) || 0;\n if (query.size !== undefined) params.size = parseInt(query.size) || 10;\n\n // Sorting\n if (query.sort !== undefined) params.sort = query.sort;\n\n // Search\n if (query.search !== undefined) params.search = query.search;\n\n // Process all other params as potential filters\n const reservedParams = ['start', 'size', 'sort', 'search', 'page'];\n Object.entries(query).forEach(([key, value]) => {\n if (!reservedParams.includes(key) && value !== undefined && value !== '') {\n // Parse value if it looks like JSON\n if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {\n try {\n params[key] = JSON.parse(value);\n } catch (e) {\n params[key] = value;\n }\n } else {\n params[key] = value;\n }\n }\n });\n\n // Update collection params\n if (Object.keys(params).length > 0) {\n this.collection.setParams({\n ...this.collection.params,\n ...params\n });\n }\n }\n\n /**\n * Sync URL with current table state\n */\n syncUrl(force = true) {\n if (!this.urlSyncEnabled || !this.collection || !this.getApp()?.router) {\n return;\n }\n\n // Get current URL params\n const currentUrl = new URL(window.location);\n const currentParams = {};\n for (const [key, value] of currentUrl.searchParams) {\n if (key !== 'page') {\n currentParams[key] = value;\n }\n }\n\n // Get desired params from collection\n const desiredParams = {};\n const collectionParams = this.collection.params || {};\n\n // Only include non-default values\n if (collectionParams.start) {\n desiredParams.start = collectionParams.start;\n }\n if (collectionParams.size) {\n desiredParams.size = collectionParams.size;\n }\n if (collectionParams.sort) {\n desiredParams.sort = collectionParams.sort;\n }\n if (collectionParams.search) {\n desiredParams.search = collectionParams.search;\n }\n\n // Include other filters\n Object.entries(collectionParams).forEach(([key, value]) => {\n if (!['start', 'size', 'sort', 'search'].includes(key) && value !== undefined && value !== '') {\n // Stringify complex values for URL\n if (typeof value === 'object') {\n desiredParams[key] = JSON.stringify(value);\n } else {\n desiredParams[key] = value;\n }\n }\n });\n\n // Check if there are any changes\n const hasChanges =\n Object.keys(desiredParams).some(key =>\n String(currentParams[key] || '') !== String(desiredParams[key] || '')\n ) ||\n Object.keys(currentParams).some(key =>\n !(key in desiredParams)\n );\n\n this.query = desiredParams;\n if (!hasChanges && !force) return;\n\n // Update URL\n this.updateBrowserUrl(desiredParams, true, false);\n }\n\n /**\n * Update status display\n */\n updateStatusDisplay() {\n if (!this.element) return;\n\n // Update last updated time\n const updatedElement = this.element.querySelector('[data-status=\"last-updated\"]');\n if (updatedElement) {\n updatedElement.textContent = this.lastUpdated || 'Never';\n }\n\n // Update record count\n const countElement = this.element.querySelector('[data-status=\"record-count\"]');\n if (countElement && this.collection) {\n const count = this.collection.meta?.count || this.collection.length();\n countElement.textContent = count;\n }\n }\n\n /**\n * Called when entering this page\n */\n async onEnter() {\n await super.onEnter();\n\n if (this.options.requiresGroup && !this.query[this.groupField] && this.getApp().activeGroup) {\n this.query[this.groupField] = this.getApp().activeGroup.id;\n }\n\n this.applyQueryToCollection();\n\n // Ensure filter pills are shown if there are active filters from URL\n if (this.tableView && this.tableView.element) {\n setTimeout(() => {\n this.tableView.updateFilterPills();\n this.tableView.updateSortIcons();\n }, 100);\n }\n }\n\n /**\n * Public method to refresh the table\n */\n async refresh() {\n await this.tableView.refresh();\n }\n\n /**\n * Public method to get selected items\n */\n getSelectedItems() {\n return this.tableView.getSelectedItems();\n }\n\n /**\n * Public method to clear selection\n */\n clearSelection() {\n this.tableView.clearSelection();\n }\n\n /**\n * Handle filter edit dialog\n */\n async handleFilterEdit(filterKey) {\n // Using statically imported Dialog\n const filterConfig = this.tableView.getAllAvailableFilters().find(f => f.key === filterKey);\n const currentValue = this.collection.params[filterKey];\n\n if (!filterConfig) return;\n\n // Build form field for the filter\n const field = {\n name: 'filter_value',\n label: filterConfig.label || filterKey,\n value: currentValue,\n ...filterConfig.config\n };\n\n const result = await Dialog.showForm({\n title: `Edit ${field.label} Filter`,\n size: 'md',\n fields: [field]\n });\n\n if (result && result.filter_value !== undefined) {\n this.tableView.setFilter(filterKey, result.filter_value);\n\n if (this.collection.restEnabled) {\n this.collection.fetch();\n }\n await this.tableView.render();\n this.syncUrl();\n }\n }\n\n /**\n * Clear all filters\n */\n clearAllFilters() {\n if (!this.collection) return;\n\n // Keep only pagination and sort params\n const { start, size, sort } = this.collection.params;\n this.collection.params = { start, size };\n if (sort) this.collection.params.sort = sort;\n\n this.syncUrl();\n\n if (this.collection.restEnabled) {\n this.collection.fetch();\n } else {\n this.tableView.render();\n }\n }\n\n async onGroupChange(group) {\n if (!group || !this.collection || !this.options.requiresGroup) return;\n this.query[this.groupField] = group.id;\n this.applyQueryToCollection();\n if (this.collection && this.collection.restEnabled) {\n this.collection.fetch();\n }\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n // Remove event listeners\n if (this.collection) {\n this.collection.off('fetch:start');\n this.collection.off('fetch:end');\n }\n\n if (this.tableView) {\n this.tableView.off('params-changed');\n this.tableView.off('table:search');\n this.tableView.off('table:sort');\n this.tableView.off('table:page');\n this.tableView.off('filter:edit');\n this.tableView.off('row:view');\n this.tableView.off('row:edit');\n this.tableView.off('row:delete');\n this.tableView.off('table:add');\n this.tableView.off('table:export');\n }\n\n await super.onBeforeDestroy();\n }\n\n /**\n * Show/hide status display\n */\n get showStatus() {\n return this.options.showStatus === true;\n }\n\n /**\n * Static factory method\n */\n static create(options = {}) {\n return new this(options);\n }\n}\n\nexport default TablePage;\n","/**\n * TabView - Simple tabbed interface component for MOJO framework\n * Displays multiple views in a tabbed interface with clean navigation\n *\n * Features:\n * - Simple tab-based navigation\n * - Child view management with proper mounting/unmounting\n * - Bootstrap 5 styling\n * - Keyboard navigation support\n * - Event-driven tab switching\n *\n * Example Usage:\n * ```javascript\n * const tabView = new TabView({\n * tabs: {\n * 'Profile': new UserProfileView({ data: userData }),\n * 'Settings': new UserSettingsView({ data: settings }),\n * 'Activity': new UserActivityView({ data: activity })\n * },\n * activeTab: 'Profile' // Optional: set initial active tab\n * });\n * ```\n */\n\nimport View from '@core/View.js';\n\nclass TabView extends View {\n constructor(options = {}) {\n const {\n tabs,\n activeTab,\n tabsClass,\n contentClass,\n minWidth,\n enableResponsive,\n tabPadding,\n dropdownStyle, // 'button' (default) or 'select'\n ...viewOptions\n } = options;\n\n super({\n tagName: 'div',\n className: 'tab-view',\n ...viewOptions\n });\n\n // Tab configuration\n this.tabs = {};\n this.tabLabels = Object.keys(this.tabs);\n this.activeTab = activeTab || this.tabLabels[0] || null;\n\n // CSS classes\n this.tabsClass = tabsClass || 'nav nav-tabs mb-3';\n this.contentClass = contentClass || 'tab-content';\n\n // Responsive configuration\n this.dropdownStyle = dropdownStyle || 'select'; // 'button' or 'select'\n this.minWidth = minWidth || 300; // Minimum width before switching to dropdown\n this.enableResponsive = enableResponsive !== false; // Default to enabled\n this.tabPadding = tabPadding || 80; // Estimated padding per tab (16px * 2)\n this.currentMode = 'tabs'; // 'tabs' or 'dropdown'\n\n // Width calculation cache\n this.tabWidthCache = new Map();\n this.lastContainerWidth = 0;\n this.resizeObserver = null;\n this._measurementSpan = null; // Reusable measurement element\n this._tabComputedStyle = null; // To store computed styles\n\n\n\n // State tracking\n this.isMobileMode = false;\n this.hasOverflow = false;\n\n // Initialize tabs by adding them as child views\n for (const [label, view] of Object.entries(tabs)) {\n this.addTab(label, view);\n }\n\n // Bind resize handler\n this.handleResize = this.handleResize.bind(this);\n }\n\n /**\n * Render the tab navigation and content areas\n */\n async renderTemplate() {\n const tabNavigation = this.buildTabNavigation();\n const tabContent = this.buildTabContent();\n\n return `\n <div class=\"tab-view-container\">\n ${tabNavigation}\n ${tabContent}\n </div>\n `;\n }\n\n /**\n * Build the tab navigation HTML - supports both tab and dropdown modes\n * @returns {string} Tab navigation HTML\n */\n buildTabNavigation() {\n if (this.tabLabels.length === 0) {\n return '';\n }\n\n if (this.currentMode === 'dropdown') {\n return this.buildDropdownNavigation();\n } else {\n return this.buildTabsNavigation();\n }\n }\n\n /**\n * Build traditional tab navigation\n * @returns {string} Tab navigation HTML\n */\n buildTabsNavigation() {\n const tabItems = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n const tabId = this.getTabId(label);\n\n return `\n <li class=\"nav-item\" role=\"presentation\">\n <button class=\"nav-link ${isActive ? 'active' : ''}\"\n id=\"${tabId}-tab\"\n data-action=\"show-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\"\n type=\"button\"\n role=\"tab\"\n aria-controls=\"${tabId}\"\n aria-selected=\"${isActive}\">\n ${this.escapeHtml(label)}\n </button>\n </li>\n `;\n }).join('');\n\n return `\n <ul class=\"${this.tabsClass}\" role=\"tablist\" data-tab-mode=\"tabs\">\n ${tabItems}\n </ul>\n `;\n }\n\n /**\n * Build dropdown navigation for mobile/narrow screens\n * @returns {string} Dropdown navigation HTML\n */\n buildDropdownNavigation() {\n const activeLabel = this.activeTab || this.tabLabels[0];\n const dropdownItems = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n return `\n <li>\n <button class=\"dropdown-item ${isActive ? 'active' : ''}\"\n data-action=\"show-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\"\n type=\"button\">\n ${this.escapeHtml(label)}\n ${isActive ? '<i class=\"bi bi-check-lg ms-2\"></i>' : ''}\n </button>\n </li>\n `;\n }).join('');\n\n let buttonHtml;\n if (this.dropdownStyle === 'select') {\n // New \"select\" style button\n buttonHtml = `\n <button class=\"btn tab-view-select-style dropdown-toggle\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n id=\"tab-dropdown-${this.id}\">\n <span class=\"tab-view-select-label\">${this.escapeHtml(activeLabel)}</span>\n </button>\n `;\n } else {\n // Original \"button\" style\n buttonHtml = `\n <button class=\"btn btn-outline-secondary dropdown-toggle w-100 w-sm-auto\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\"\n id=\"tab-dropdown-${this.id}\">\n <i class=\"bi bi-list me-2\"></i>\n ${this.escapeHtml(activeLabel)}\n </button>\n `;\n }\n\n return `\n <div class=\"dropdown mb-3\" data-tab-mode=\"dropdown\">\n ${buttonHtml}\n <ul class=\"dropdown-menu\" aria-labelledby=\"tab-dropdown-${this.id}\">\n ${dropdownItems}\n </ul>\n </div>\n `;\n }\n\n /**\n * Build mobile dropdown navigation\n * @returns {string} Mobile dropdown HTML\n */\n buildMobileDropdownNavigation() {\n const activeLabel = this.activeTab || this.tabLabels[0];\n const dropdownItems = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n return `\n <li>\n <button class=\"dropdown-item ${isActive ? 'active' : ''}\"\n data-action=\"show-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\"\n type=\"button\">\n ${this.escapeHtml(label)}\n ${isActive ? '<i class=\"bi bi-check ms-2\"></i>' : ''}\n </button>\n </li>\n `;\n }).join('');\n\n return `\n <div class=\"dropdown mb-3\" data-tab-navigation=\"mobile\">\n <button class=\"btn btn-outline-secondary dropdown-toggle w-100 text-start\"\n type=\"button\"\n data-bs-toggle=\"dropdown\"\n aria-expanded=\"false\">\n <i class=\"bi bi-list me-2\"></i>\n ${this.escapeHtml(activeLabel)}\n </button>\n <ul class=\"dropdown-menu w-100\">\n ${dropdownItems}\n </ul>\n </div>\n `;\n }\n\n /**\n * Determine if mobile dropdown should be used\n * @returns {boolean} True if mobile dropdown should be used\n */\n shouldUseMobileDropdown() {\n if (!this.enableMobileDropdown) return false;\n\n // Check viewport width\n const viewportWidth = window.innerWidth;\n if (viewportWidth < this.mobileBreakpoint) {\n return true;\n }\n\n // Check for overflow in desktop mode\n return this.hasOverflow && viewportWidth < 992; // Bootstrap lg breakpoint\n }\n\n /**\n * Build the tab content area HTML\n * @returns {string} Tab content HTML\n */\n buildTabContent() {\n if (this.tabLabels.length === 0) {\n return '<div class=\"alert alert-info\">No tabs to display</div>';\n }\n\n const tabPanes = this.tabLabels.map(label => {\n const isActive = label === this.activeTab;\n const tabId = this.getTabId(label);\n\n return `\n <div class=\"tab-pane fade ${isActive ? 'show active' : ''}\"\n id=\"${tabId}\"\n role=\"tabpanel\"\n aria-labelledby=\"${tabId}-tab\"\n data-tab-label=\"${this.escapeHtml(label)}\">\n <div data-container=\"${tabId}-content\"></div>\n </div>\n `;\n }).join('');\n\n return `\n <div class=\"${this.contentClass}\">\n ${tabPanes}\n </div>\n `;\n }\n\n /**\n * Generate a safe DOM ID for a tab\n * @param {string} label - Tab label\n * @returns {string} Safe DOM ID\n */\n getTabId(label) {\n return `tab-${label.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${this.id}`;\n }\n\n /**\n * Show a specific tab\n * @param {string} tabLabel - Label of the tab to show\n * @returns {Promise<boolean>} True if tab was shown successfully\n */\n async showTab(tabLabel, options = {}) {\n const { force = false } = options;\n\n // Validate tab exists\n if (!this.tabs[tabLabel]) {\n console.warn(`TabView: Tab \"${tabLabel}\" does not exist`);\n return false;\n }\n\n // Skip if already active and not being forced\n // This prevents re-rendering the same tab on repeated clicks, but allows\n // for forced re-mounting after a parent view render.\n if (this.activeTab === tabLabel && !force) {\n // As a safeguard, ensure the view is actually in the DOM.\n // If not, proceed to re-mount it.\n const activeView = this.tabs[tabLabel];\n if (activeView && activeView.isMounted() && this.element.contains(activeView.element)) {\n return true;\n }\n }\n\n const previousTab = this.activeTab;\n this.activeTab = tabLabel;\n\n try {\n // Update tab navigation\n await this.updateTabNavigation(tabLabel, previousTab);\n\n // Update tab content\n await this.updateTabContent(tabLabel, previousTab);\n\n // Emit tab change event\n this.emit('tab:changed', {\n activeTab: tabLabel,\n previousTab: previousTab\n });\n\n return true;\n } catch (error) {\n console.error('TabView: Error showing tab:', error);\n this.activeTab = previousTab; // Revert on error\n return false;\n }\n }\n\n /**\n * Update tab navigation visual state\n * @param {string} activeTabLabel - New active tab\n * @param {string} previousTabLabel - Previous active tab\n */\n async updateTabNavigation(activeTabLabel, previousTabLabel) {\n if (!this.element) return;\n\n // In dropdown mode, re-render the navigation to update the button label and items\n if (this.currentMode === 'dropdown') {\n await this.reRenderNavigation();\n return; // Re-rendering handles all visual updates for navigation\n }\n\n // In tabs mode, just toggle classes for efficiency\n if (previousTabLabel) {\n const prevTabButton = this.element.querySelector(`[data-tab-label=\"${previousTabLabel}\"]`);\n if (prevTabButton) {\n prevTabButton.classList.remove('active');\n prevTabButton.setAttribute('aria-selected', 'false');\n }\n }\n\n // Add active state to new tab\n const activeTabButton = this.element.querySelector(`[data-tab-label=\"${activeTabLabel}\"]`);\n if (activeTabButton) {\n activeTabButton.classList.add('active');\n activeTabButton.setAttribute('aria-selected', 'true');\n }\n }\n\n /**\n * Update tab content area and manage child views\n * @param {string} activeTabLabel - New active tab\n * @param {string} previousTabLabel - Previous active tab\n */\n async updateTabContent(activeTabLabel, previousTabLabel) {\n if (!this.element) return;\n\n const activeTabId = this.getTabId(activeTabLabel);\n const previousTabId = previousTabLabel ? this.getTabId(previousTabLabel) : null;\n\n // Hide previous tab pane\n if (previousTabId) {\n const prevPane = this.element.querySelector(`#${previousTabId}`);\n if (prevPane) {\n prevPane.classList.remove('show', 'active');\n }\n }\n\n // Show active tab pane\n const activePane = this.element.querySelector(`#${activeTabId}`);\n if (activePane) {\n activePane.classList.add('show', 'active');\n }\n\n // Mount the active tab's view\n const activeView = this.tabs[activeTabLabel];\n if (activeView) {\n const container = this.element.querySelector(`[data-container=\"${activeTabId}-content\"]`);\n if (container && !activeView.isMounted()) {\n await activeView.render(true, container);\n }\n if (activeView.onTabActivated) {\n await activeView.onTabActivated();\n }\n }\n }\n\n /**\n * Handle tab click events\n * @param {Event} event - Click event\n * @param {HTMLElement} element - Clicked element\n */\n async onActionShowTab(event, element) {\n const tabLabel = element.getAttribute('data-tab-label');\n if (tabLabel) {\n await this.showTab(tabLabel);\n }\n }\n\n /**\n * Initialize measurement styles by reading computed styles from a rendered tab\n */\n _initializeMeasurementStyles() {\n if (!this.element || this._tabComputedStyle) return;\n\n const tabButton = this.element.querySelector('.nav-link');\n if (tabButton && typeof window.getComputedStyle === 'function') {\n const style = window.getComputedStyle(tabButton);\n this._tabComputedStyle = {\n font: style.font,\n letterSpacing: style.letterSpacing,\n };\n\n // Calculate horizontal padding from computed styles\n const paddingLeft = parseFloat(style.paddingLeft) || 0;\n const paddingRight = parseFloat(style.paddingRight) || 0;\n this.tabPadding = paddingLeft + paddingRight + 12; // Update with real value\n }\n }\n\n /**\n * Initialize active tab after rendering\n */\n async onAfterRender() {\n await super.onAfterRender();\n\n // Initialize styles for accurate width calculation before setting up responsive handling\n this._initializeMeasurementStyles();\n\n // Set up responsive behavior\n if (this.enableResponsive) {\n this.setupResponsiveHandling();\n }\n\n // Show the active tab after initial render. Forcing ensures the content is\n // correctly re-mounted if the TabView itself was re-rendered.\n if (this.activeTab && this.tabs[this.activeTab]) {\n await this.showTab(this.activeTab, { force: true });\n }\n }\n\n /**\n * Mount child views after tab view is mounted\n */\n async onAfterMount() {\n await super.onAfterMount();\n\n // The active tab's content is now mounted via the onAfterRender -> showTab flow,\n // which correctly handles both initial renders and subsequent re-renders.\n // The logic previously here was redundant.\n }\n\n /**\n * Clean up child views when destroying\n */\n async onBeforeDestroy() {\n await super.onBeforeDestroy();\n\n // Clean up resize observer\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n\n // Remove window resize listener\n if (typeof window !== 'undefined') {\n window.removeEventListener('resize', this.handleResize);\n }\n\n // Remove measurement span if it exists\n if (this._measurementSpan && this._measurementSpan.parentElement) {\n this._measurementSpan.parentElement.removeChild(this._measurementSpan);\n }\n this._measurementSpan = null;\n\n // Destroy all child tab views\n for (const [label, view] of Object.entries(this.tabs)) {\n if (view && typeof view.destroy === 'function') {\n await view.destroy();\n }\n }\n }\n\n /**\n * Get the currently active tab label\n * @returns {string|null} Active tab label\n */\n getActiveTab() {\n return this.activeTab;\n }\n\n /**\n * Get all tab labels\n * @returns {string[]} Array of tab labels\n */\n getTabLabels() {\n return [...this.tabLabels];\n }\n\n /**\n * Get a specific tab's view by label\n * @param {string} label - Tab label\n * @returns {View|null} The tab's view instance or null if not found\n */\n getTab(label) {\n return this.tabs[label] || null;\n }\n\n /**\n * Add a new tab\n * @param {string} label - Tab label\n * @param {View} view - View instance for tab content\n * @param {boolean} makeActive - Whether to make this tab active\n * @returns {Promise<boolean>} True if tab was added successfully\n */\n async addTab(label, view, makeActive = false) {\n if (this.tabs[label]) {\n console.warn(`TabView: Tab \"${label}\" already exists`);\n return false;\n }\n\n if (view.options.permissions && !this.getApp().activeUser.hasPerm(view.options.permissions)) {\n return false;\n }\n\n this.tabs[label] = view;\n // this.addChild(view);\n view.containerId = this.getTabId(label);\n view.parent = this;\n this.tabLabels = Object.keys(this.tabs);\n\n // Set as active tab if it's the first tab or explicitly requested\n if (!this.activeTab || makeActive) {\n this.activeTab = label;\n }\n\n // Re-render if mounted\n if (this.isMounted()) {\n await this.render();\n\n if (makeActive || this.activeTab === label) {\n await this.showTab(label);\n }\n }\n\n this.emit('tab:added', { label, view });\n return true;\n }\n\n /**\n * Remove a tab\n * @param {string} label - Tab label to remove\n * @returns {Promise<boolean>} True if tab was removed successfully\n */\n async removeTab(label) {\n if (!this.tabs[label]) {\n console.warn(`TabView: Tab \"${label}\" does not exist`);\n return false;\n }\n\n const view = this.tabs[label];\n\n // Destroy the view\n if (view && typeof view.destroy === 'function') {\n await view.destroy();\n }\n\n // Remove from tabs\n delete this.tabs[label];\n this.tabLabels = Object.keys(this.tabs);\n\n // Handle active tab removal\n if (this.activeTab === label) {\n this.activeTab = this.tabLabels[0] || null;\n }\n\n // Re-render if mounted\n if (this.isMounted()) {\n await this.render();\n\n if (this.activeTab) {\n await this.showTab(this.activeTab);\n }\n }\n\n this.emit('tab:removed', { label, view });\n return true;\n }\n\n /**\n * Calculate estimated width needed for a tab label\n * @param {string} label - Tab label text\n * @returns {number} Estimated width in pixels\n */\n calculateTabWidth(label) {\n if (this.tabWidthCache.has(label)) {\n return this.tabWidthCache.get(label);\n }\n\n // Fallback for non-browser environments\n if (typeof document === 'undefined') {\n const estimatedWidth = label.length * 8 + this.tabPadding; // Original fallback\n this.tabWidthCache.set(label, estimatedWidth);\n return estimatedWidth;\n }\n\n // Create a reusable measurement span if it doesn't exist\n if (!this._measurementSpan) {\n this._measurementSpan = document.createElement('span');\n this._measurementSpan.style.visibility = 'hidden';\n this._measurementSpan.style.position = 'absolute';\n this._measurementSpan.style.whiteSpace = 'nowrap';\n }\n\n const span = this._measurementSpan;\n\n // Apply computed styles if available, otherwise use defaults\n if (this._tabComputedStyle) {\n span.style.font = this._tabComputedStyle.font;\n span.style.letterSpacing = this._tabComputedStyle.letterSpacing;\n } else {\n // Fallback to original hardcoded styles if computed style not yet available\n span.style.fontSize = '14px';\n span.style.fontFamily = 'system-ui, -apple-system, sans-serif';\n }\n\n span.textContent = label;\n document.body.appendChild(span);\n const width = span.offsetWidth + this.tabPadding;\n document.body.removeChild(span);\n\n this.tabWidthCache.set(label, width);\n return width;\n }\n\n /**\n * Calculate total width needed for all tabs\n * @returns {number} Total width in pixels\n */\n getTotalTabWidth() {\n return this.tabLabels.reduce((total, label) => {\n return total + this.calculateTabWidth(label);\n }, 0);\n }\n\n /**\n * Get current container width\n * @returns {number} Container width in pixels\n */\n getContainerWidth() {\n if (!this.element) {\n return this.minWidth;\n }\n\n const container = this.element.parentElement || this.element;\n return container.offsetWidth || this.minWidth;\n }\n\n /**\n * Determine if dropdown mode should be used\n * @returns {boolean} True if dropdown mode should be used\n */\n shouldUseDropdown() {\n if (!this.enableResponsive) {\n return false;\n }\n\n const containerWidth = this.getContainerWidth();\n const totalTabWidth = this.getTotalTabWidth();\n\n // Use dropdown if tabs would overflow or container is too narrow\n return containerWidth < Math.max(totalTabWidth, this.minWidth);\n }\n\n /**\n * Setup responsive handling with ResizeObserver\n */\n setupResponsiveHandling() {\n if (!this.element || !this.enableResponsive) {\n return;\n }\n\n // Set initial mode\n this.updateNavigationMode();\n\n // Use ResizeObserver for better performance\n if (typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => {\n this.handleResize();\n });\n\n const container = this.element.parentElement || this.element;\n this.resizeObserver.observe(container);\n } else {\n // Fallback to window resize\n window.addEventListener('resize', this.handleResize);\n }\n }\n\n /**\n * Handle resize events\n */\n async handleResize() {\n const containerWidth = this.getContainerWidth();\n\n // Only update if width changed significantly (avoid excessive updates)\n if (Math.abs(containerWidth - this.lastContainerWidth) > 50) {\n this.lastContainerWidth = containerWidth;\n await this.updateNavigationMode();\n }\n }\n\n /**\n * Update navigation mode based on current space\n */\n async updateNavigationMode() {\n const shouldUseDropdown = this.shouldUseDropdown();\n const newMode = shouldUseDropdown ? 'dropdown' : 'tabs';\n\n if (newMode !== this.currentMode) {\n this.currentMode = newMode;\n\n // Re-render navigation if mounted\n if (this.isMounted()) {\n await this.reRenderNavigation();\n }\n\n // Emit mode change event\n this.emit('navigation:modeChanged', {\n mode: this.currentMode,\n containerWidth: this.getContainerWidth(),\n totalTabWidth: this.getTotalTabWidth()\n });\n }\n }\n\n /**\n * Re-render just the navigation part\n */\n async reRenderNavigation() {\n if (!this.element) return;\n\n const navigationContainer = this.element.querySelector('[data-tab-mode]');\n if (navigationContainer) {\n const newNavigation = this.buildTabNavigation();\n navigationContainer.outerHTML = newNavigation;\n }\n }\n\n /**\n * Get current navigation mode\n * @returns {string} Current mode ('tabs' or 'dropdown')\n */\n getNavigationMode() {\n return this.currentMode;\n }\n\n /**\n * Force a specific navigation mode\n * @param {string} mode - 'tabs' or 'dropdown'\n */\n async setNavigationMode(mode) {\n if (mode !== 'tabs' && mode !== 'dropdown') {\n console.warn('TabView: Invalid navigation mode. Use \"tabs\" or \"dropdown\"');\n return;\n }\n\n this.currentMode = mode;\n\n if (this.isMounted()) {\n await this.reRenderNavigation();\n }\n }\n\n /**\n * Clear the tab width cache (useful when tab labels change)\n */\n clearWidthCache() {\n this.tabWidthCache.clear();\n }\n\n /**\n * Static factory method\n * @param {object} options - TabView options\n * @returns {TabView} New TabView instance\n */\n static create(options = {}) {\n return new TabView(options);\n }\n}\n\nexport default TabView;\n","import View from '@core/View.js';\n\nclass FilePreviewView extends View {\n constructor(options = {}) {\n super({\n className: 'file-preview',\n ...options\n });\n this.file = options.file || {};\n this.isImage = this.file.content_type?.startsWith('image/');\n this.isPdf = this.file.content_type === 'application/pdf';\n }\n\n getTemplate() {\n return `\n <div class=\"file-preview-item card card-body p-2 mt-2\">\n <div class=\"d-flex align-items-center\">\n <div class=\"flex-shrink-0\">\n ${this.isImage ? `<img src=\"${this.file.thumbnailUrl || this.file.url}\" class=\"rounded\" style=\"width: 40px; height: 40px; object-fit: cover;\">` : `<i class=\"bi bi-file-earmark-text fs-2 text-secondary\"></i>`}\n </div>\n <div class=\"flex-grow-1 ms-3\">\n <div class=\"fw-bold text-truncate\">{{file.filename}}</div>\n <div class=\"small text-muted\">{{file.file_size|filesize}}</div>\n </div>\n <div class=\"flex-shrink-0\">\n <button class=\"btn btn-sm btn-outline-primary\" data-action=\"view-file\">View</button>\n </div>\n </div>\n </div>\n `;\n }\n\n async onActionViewFile() {\n if (this.isImage) {\n // Check if lightbox extension is available\n const LightboxGallery = window.MOJO?.plugins?.LightboxGallery;\n \n if (LightboxGallery) {\n LightboxGallery.show({ src: this.file.url, alt: this.file.filename });\n } else {\n // Fallback: open in new tab\n window.open(this.file.url, '_blank');\n }\n } else if (this.isPdf) {\n // Check if lightbox extension is available\n const PDFViewer = window.MOJO?.plugins?.PDFViewer;\n \n if (PDFViewer) {\n PDFViewer.showDialog(this.file.url, { title: this.file.filename });\n } else {\n // Fallback: open in new tab\n window.open(this.file.url, '_blank');\n }\n } else {\n window.open(this.file.url, '_blank');\n }\n }\n}\n\nexport default FilePreviewView;\n","import View from '@core/View.js';\nimport FilePreviewView from '@core/views/data/FilePreviewView.js';\n\n/**\n * ChatMessageView - Individual message display with theme support\n * \n * Supports two themes:\n * - 'compact': List-based admin/activity feed style\n * - 'bubbles': Chat bubble style with left/right positioning\n */\nclass ChatMessageView extends View {\n constructor(options = {}) {\n super({\n className: 'chat-message',\n ...options\n });\n \n this.message = options.message || {};\n this.theme = options.theme || 'compact';\n this.isCurrentUser = options.isCurrentUser || false;\n \n // Add theme-specific class\n if (this.theme === 'bubbles') {\n this.className += this.isCurrentUser ? ' message-right' : ' message-left';\n }\n }\n\n getTemplate() {\n // System event messages (same for both themes)\n if (this.message.type === 'system_event') {\n return `\n <div class=\"chat-message-system text-center text-muted small py-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n {{message.content}} on {{message.timestamp|datetime}}\n </div>\n `;\n }\n\n // Theme-specific templates\n if (this.theme === 'bubbles') {\n return this.getBubblesTemplate();\n } else {\n return this.getCompactTemplate();\n }\n }\n\n /**\n * Get compact theme template (Option 4 - Admin/Activity Feed Style)\n */\n getCompactTemplate() {\n const userClass = this.isCurrentUser ? 'bg-primary' : 'bg-secondary';\n \n return `\n <div class=\"message-item\">\n <div class=\"message-avatar ${userClass}\">\n {{#message.author.avatarUrl}}\n <img src=\"{{message.author.avatarUrl}}\" alt=\"{{message.author.name}}\" class=\"w-100 h-100 rounded-circle\">\n {{/message.author.avatarUrl}}\n {{^message.author.avatarUrl}}\n {{message.author.name|initials}}\n {{/message.author.avatarUrl}}\n </div>\n <div class=\"message-content\">\n <div class=\"message-header\">\n <div class=\"message-author\">\n {{message.author.name}}\n {{#isCurrentUser}}\n <span class=\"badge bg-primary badge-sm ms-1\">You</span>\n {{/isCurrentUser}}\n </div>\n <div class=\"message-time text-muted\">{{message.timestamp|relative}}</div>\n </div>\n <div class=\"message-text\">{{{message.content}}}</div>\n <div data-container=\"attachments\"></div>\n </div>\n </div>\n `;\n }\n\n /**\n * Get bubbles theme template (Option 1 - Modern Chat Bubbles)\n */\n getBubblesTemplate() {\n return `\n <div class=\"message-bubble-wrapper\">\n <div class=\"message-meta\">\n <strong>{{message.author.name}}</strong>\n <span class=\"text-muted\">· {{message.timestamp|relative}}</span>\n </div>\n <div class=\"message-bubble\">\n <div class=\"message-text\">{{{message.content}}}</div>\n <div data-container=\"attachments\"></div>\n </div>\n </div>\n `;\n }\n\n async onAfterRender() {\n // Render attachments if any\n if (this.message.attachments && this.message.attachments.length > 0) {\n const attachmentsContainer = this.element.querySelector('[data-container=\"attachments\"]');\n if (attachmentsContainer) {\n this.message.attachments.forEach(file => {\n const filePreview = new FilePreviewView({ file });\n this.addChild(filePreview);\n filePreview.render(true, attachmentsContainer);\n });\n }\n }\n }\n}\n\nexport default ChatMessageView;\n","import View from '@core/View.js';\nimport applyFileDropMixin from '@core/mixins/FileDropMixin.js';\nimport { File } from '@core/models/Files.js';\n\n/**\n * ChatInputView - Input area with file drop support and attachment preview\n */\nclass ChatInputView extends View {\n constructor(options = {}) {\n super({\n className: 'chat-input-view',\n ...options\n });\n\n this.placeholder = options.placeholder || 'Type a message...';\n this.buttonText = options.buttonText || 'Send';\n this.attachments = []; // Array of uploaded file data\n this.pendingUploads = new Map(); // Track in-progress uploads\n }\n\n getTemplate() {\n return `\n <div class=\"chat-input-container\">\n <div class=\"chat-input-attachments\" data-container=\"attachments\"></div>\n <div class=\"chat-input-wrapper\">\n <textarea\n class=\"chat-input form-control\"\n placeholder=\"${this.placeholder}\"\n rows=\"2\"></textarea>\n <button class=\"chat-send-btn btn btn-primary\" data-action=\"send-message\" type=\"button\">\n <i class=\"bi bi-send-fill\"></i>\n <span class=\"spinner-border spinner-border-sm d-none\" role=\"status\" aria-hidden=\"true\"></span>\n </button>\n </div>\n <div class=\"chat-input-footer\">\n <small class=\"text-muted\">\n <i class=\"bi bi-paperclip\"></i>\n Drag & drop files to attach\n </small>\n </div>\n </div>\n `;\n }\n\n async onAfterRender() {\n // Enable file drop on the entire input container\n this.enableFileDrop({\n dropZoneSelector: '.chat-input-container',\n multiple: true,\n acceptedTypes: ['*/*'], // Accept all file types\n visualFeedback: true,\n dragOverClass: 'drag-over',\n dragActiveClass: 'drag-active'\n });\n\n // Auto-resize textarea as user types and handle Enter key\n const textarea = this.element.querySelector('.chat-input');\n if (textarea) {\n textarea.addEventListener('input', () => this.autoResizeTextarea(textarea));\n textarea.addEventListener('keydown', (e) => this.handleKeydown(e));\n }\n }\n\n /**\n * Handle textarea keydown (send on Enter without Shift)\n */\n handleKeydown(event) {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n this.onActionSendMessage(event, event.target);\n }\n }\n\n /**\n * Handle file drop\n * @param {File[]} files - Dropped files\n */\n async onFileDrop(files) {\n for (const file of files) {\n await this.uploadFile(file);\n }\n }\n\n /**\n * Upload a file\n * @param {File} file - File to upload\n */\n async uploadFile(file) {\n const fileModel = new File();\n const uploadId = Date.now() + Math.random();\n\n // Add preview immediately\n this.addFilePreview(uploadId, file, 0);\n this.pendingUploads.set(uploadId, { file, fileModel });\n\n try {\n const result = await fileModel.upload({\n file: file,\n onProgress: (progress) => {\n this.updateFileProgress(uploadId, progress);\n },\n onComplete: (uploadResult) => {\n this.handleUploadComplete(uploadId, fileModel);\n }\n });\n\n } catch (error) {\n console.error('File upload failed:', error);\n this.handleUploadError(uploadId, error);\n }\n }\n\n /**\n * Add file preview to UI\n * @param {string} uploadId - Unique upload ID\n * @param {File} file - File object\n * @param {number} progress - Upload progress (0-100)\n */\n addFilePreview(uploadId, file, progress) {\n const container = this.element.querySelector('[data-container=\"attachments\"]');\n if (!container) return;\n\n const preview = document.createElement('div');\n preview.className = 'attachment-preview';\n preview.dataset.uploadId = uploadId;\n preview.innerHTML = `\n <div class=\"attachment-info\">\n <i class=\"bi bi-file-earmark\"></i>\n <span class=\"attachment-name\">${this.escapeHtml(file.name)}</span>\n <span class=\"attachment-size\">(${this.formatFileSize(file.size)})</span>\n </div>\n <div class=\"attachment-progress\">\n <div class=\"progress\" style=\"height: 4px;\">\n <div class=\"progress-bar\" role=\"progressbar\" style=\"width: ${progress}%\"></div>\n </div>\n </div>\n <button class=\"attachment-remove btn btn-sm btn-link text-danger\" data-action=\"remove-attachment\" data-upload-id=\"${uploadId}\" type=\"button\">\n <i class=\"bi bi-x\"></i>\n </button>\n `;\n\n container.appendChild(preview);\n }\n\n /**\n * Update file upload progress\n * @param {string} uploadId - Upload ID\n * @param {number} progress - Progress (0-100)\n */\n updateFileProgress(uploadId, progress) {\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n const progressBar = preview.querySelector('.progress-bar');\n if (progressBar) {\n progressBar.style.width = `${progress}%`;\n }\n }\n }\n\n /**\n * Handle upload completion\n * @param {string} uploadId - Upload ID\n * @param {Object} result - Upload result data (contains file.id)\n */\n handleUploadComplete(uploadId, fileModel) {\n // Store the file data with its ID\n this.attachments.push({\n id: fileModel.id,\n name: fileModel.get(\"name\"),\n uploadId: uploadId\n });\n this.pendingUploads.delete(uploadId);\n\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n preview.classList.add('upload-complete');\n const progressContainer = preview.querySelector('.attachment-progress');\n if (progressContainer) {\n progressContainer.remove();\n }\n }\n }\n\n /**\n * Handle upload error\n * @param {string} uploadId - Upload ID\n * @param {Error} error - Error object\n */\n handleUploadError(uploadId, error) {\n this.pendingUploads.delete(uploadId);\n\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n preview.classList.add('upload-error');\n preview.querySelector('.attachment-info').innerHTML +=\n `<span class=\"text-danger ms-2\">Upload failed</span>`;\n }\n }\n\n /**\n * Remove attachment\n */\n async onActionRemoveAttachment(event, element) {\n const uploadId = element.dataset.uploadId;\n\n // Remove from pending uploads\n this.pendingUploads.delete(uploadId);\n\n // Remove from completed attachments\n const preview = this.element.querySelector(`[data-upload-id=\"${uploadId}\"]`);\n if (preview) {\n // TODO: Get the file ID from the preview and remove from attachments array\n preview.remove();\n }\n }\n\n\n\n /**\n * Send message\n */\n async onActionSendMessage(event, element) {\n const textarea = this.element.querySelector('.chat-input');\n const text = textarea.value.trim();\n\n // Don't send if empty and no attachments\n if (!text && this.attachments.length === 0) {\n return;\n }\n\n // Don't send if uploads are pending\n if (this.pendingUploads.size > 0) {\n // TODO: Show message that uploads are in progress\n return;\n }\n\n // Show busy state\n this.setBusy(true);\n\n // Emit event with message data\n this.emit('message:send', {\n text: text,\n files: this.attachments\n });\n\n // Note: Don't clear here - let the parent ChatView call clearInput() after successful send\n }\n\n /**\n * Set busy state (show/hide spinner)\n * @param {boolean} busy - Whether to show busy state\n */\n setBusy(busy) {\n const button = this.element.querySelector('.chat-send-btn');\n const icon = button.querySelector('.bi-send-fill');\n const spinner = button.querySelector('.spinner-border');\n\n if (busy) {\n button.disabled = true;\n icon.classList.add('d-none');\n spinner.classList.remove('d-none');\n } else {\n button.disabled = false;\n icon.classList.remove('d-none');\n spinner.classList.add('d-none');\n }\n }\n\n /**\n * Clear input and attachments\n */\n clearInput() {\n const textarea = this.element.querySelector('.chat-input');\n if (textarea) {\n textarea.value = '';\n textarea.style.height = 'auto';\n }\n\n const container = this.element.querySelector('[data-container=\"attachments\"]');\n if (container) {\n container.innerHTML = '';\n }\n\n this.attachments = [];\n this.pendingUploads.clear();\n \n // Reset busy state\n this.setBusy(false);\n }\n\n /**\n * Auto-resize textarea based on content\n * @param {HTMLTextAreaElement} textarea\n */\n autoResizeTextarea(textarea) {\n textarea.style.height = 'auto';\n textarea.style.height = Math.min(textarea.scrollHeight, 150) + 'px';\n }\n\n /**\n * Format file size for display\n * @param {number} bytes\n * @returns {string}\n */\n formatFileSize(bytes) {\n if (bytes === 0) return '0 B';\n const k = 1024;\n const sizes = ['B', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];\n }\n\n /**\n * Escape HTML to prevent XSS\n * @param {string} text\n * @returns {string}\n */\n escapeHtml(text) {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n}\n\n// Apply FileDropMixin\napplyFileDropMixin(ChatInputView);\n\nexport default ChatInputView;\n","import View from '@core/View.js';\nimport ChatMessageView from './ChatMessageView.js';\nimport ChatInputView from './ChatInputView.js';\n\n/**\n * ChatView - Modern chat interface with theme support\n * \n * Themes:\n * - 'compact' (default): Admin/activity feed style, list-based layout\n * - 'bubbles': Modern chat bubbles with left/right positioning\n * \n * Usage:\n * const chat = new ChatView({\n * adapter: myAdapter,\n * theme: 'compact', // or 'bubbles'\n * currentUserId: 123,\n * inputPlaceholder: 'Add a comment...',\n * inputButtonText: 'Send'\n * });\n */\nclass ChatView extends View {\n constructor(options = {}) {\n super({\n className: 'chat-view',\n ...options\n });\n \n this.adapter = options.adapter;\n this.theme = options.theme || 'compact'; // 'compact' or 'bubbles'\n this.currentUserId = options.currentUserId;\n this.inputPlaceholder = options.inputPlaceholder || 'Type a message...';\n this.inputButtonText = options.inputButtonText || 'Send';\n this.messages = [];\n this.messageViews = new Map(); // Track message views by ID\n }\n\n getTemplate() {\n return `\n <div class=\"chat-container chat-theme-${this.theme}\">\n <div class=\"chat-messages\" data-container=\"messages\"></div>\n <div class=\"chat-input-wrapper\" data-container=\"input\"></div>\n </div>\n `;\n }\n\n async onInit() {\n // Initial fetch of messages\n this.messages = await this.adapter.fetch();\n \n // Create input view\n this.inputView = new ChatInputView({\n containerId: 'input',\n placeholder: this.inputPlaceholder,\n buttonText: this.inputButtonText\n });\n this.addChild(this.inputView);\n \n // Listen for new messages\n this.inputView.on('message:send', async (data) => {\n await this.handleSendMessage(data);\n });\n }\n\n async onAfterRender() {\n // Build message views\n this._buildMessageViews();\n \n // Render children (like ListView does)\n await this._renderChildren();\n \n // Scroll to bottom\n this.scrollToBottom();\n }\n\n /**\n * Render child message views (similar to ListView._renderChildren)\n * @private\n */\n async _renderChildren() {\n await super._renderChildren();\n const messagesContainer = this.element.querySelector('[data-container=\"messages\"]');\n if (!messagesContainer) {\n console.error('ChatView: messages container not found');\n return;\n }\n \n // Append each message view to the container and render it\n this.messageViews.forEach((messageView) => {\n messagesContainer.appendChild(messageView.element);\n messageView.render(false);\n });\n }\n\n /**\n * Build message views for all messages (similar to ListView._buildItems)\n * @private\n */\n _buildMessageViews() {\n if (!this.messages || this.messages.length === 0) return;\n \n this.messages.forEach(message => {\n if (!this.messageViews.has(message.id)) {\n this._createMessageView(message);\n }\n });\n }\n\n /**\n * Create a message view (similar to ListView._createItemView)\n * @private\n */\n _createMessageView(message) {\n if (this.messageViews.has(message.id)) return;\n \n const isCurrentUser = message.author && message.author.id === this.currentUserId;\n \n const messageView = new ChatMessageView({\n message: message,\n theme: this.theme,\n isCurrentUser: isCurrentUser\n });\n \n this.addChild(messageView);\n this.messageViews.set(message.id, messageView);\n \n return messageView;\n }\n\n /**\n * Add a new message to the chat (for real-time updates)\n * @param {Object} message - Message data\n * @param {boolean} scroll - Whether to scroll to bottom after adding\n */\n addMessage(message, scroll = true) {\n if (this.messageViews.has(message.id)) return;\n \n const messageView = this._createMessageView(message);\n \n // If already rendered, append to DOM immediately\n if (this.isMounted()) {\n const messagesContainer = this.element.querySelector('[data-container=\"messages\"]');\n if (messagesContainer) {\n messagesContainer.appendChild(messageView.element);\n messageView.render(false);\n }\n }\n \n if (scroll) {\n this.scrollToBottom();\n }\n }\n\n /**\n * Handle sending a new message\n * @param {Object} data - Message data {text, files}\n * @private\n */\n async handleSendMessage(data) {\n try {\n // If there's text, send it as a note\n if (data.text && data.text.trim()) {\n const result = await this.adapter.addNote({\n text: data.text,\n files: data.files && data.files.length > 0 ? [data.files[0]] : []\n });\n \n if (!result.success) {\n throw new Error('Failed to send message');\n }\n }\n \n // If there are multiple files, or files without text, create a note for each\n const startIndex = (data.text && data.text.trim() && data.files.length > 0) ? 1 : 0;\n \n for (let i = startIndex; i < (data.files?.length || 0); i++) {\n const file = data.files[i];\n const result = await this.adapter.addNote({\n text: '', // Empty text, just the file\n files: [file]\n });\n \n if (!result.success) {\n console.error('Failed to upload file:', file);\n }\n }\n \n // Fetch updated messages\n this.messages = await this.adapter.fetch();\n \n // Find the new message(s) and add them\n this.messages.forEach(message => {\n if (!this.messageViews.has(message.id)) {\n this.addMessage(message, true);\n }\n });\n \n // Clear input (this also resets busy state)\n this.inputView.clearInput();\n \n } catch (error) {\n console.error('Failed to send message:', error);\n // Reset busy state on error\n this.inputView.setBusy(false);\n // TODO: Show error feedback to user\n }\n }\n\n /**\n * Scroll chat to bottom\n */\n scrollToBottom() {\n const container = this.element.querySelector('.chat-messages');\n if (container) {\n requestAnimationFrame(() => {\n container.scrollTop = container.scrollHeight;\n });\n }\n }\n\n /**\n * Clear all messages\n */\n clearMessages() {\n this.messageViews.forEach(view => view.destroy());\n this.messageViews.clear();\n this.messages = [];\n \n const container = this.element.querySelector('[data-container=\"messages\"]');\n if (container) {\n container.innerHTML = '';\n }\n }\n\n /**\n * Refresh messages from adapter\n */\n async refresh() {\n this.clearMessages();\n this.messages = await this.adapter.fetch();\n this._buildMessageViews();\n \n if (this.isMounted()) {\n await this._renderChildren();\n this.scrollToBottom();\n }\n }\n}\n\nexport default ChatView;\n"],"names":["S3Bucket","Model","constructor","data","super","endpoint","S3BucketList","Collection","options","ModelClass","size","EmailDomain","onboard","this","id","showError","success","status","error","url","buildUrl","rest","POST","params","err","message","audit","method","toUpperCase","GET","reconcile","onboardById","model","auditById","reconcileById","EmailDomainList","Mailbox","sendEmail","async","MailboxList","MailboxForms","create","title","fields","type","name","label","labelField","valueField","maxItems","placeholder","emptyFetch","required","debounceMs","columns","defaultValue","help","edit","SentMessage","SentMessageList","EmailTemplate","EmailTemplateList","ProgressView","View","template","filename","filesize","filesizeFormatted","dataFormatter","pipe","progress","percentage","loaded","total","loadedFormatted","totalFormatted","showCancel","onCancel","cancelled","completed","getTemplate","updateProgress","progressInfo","render","markCompleted","markFailed","markCancelled","onActionCancel","action","event","element","disabled","emit","console","setFilename","setFilesize","getPercentage","isCompleted","isCancelled","getStats","FileUpload","fileModel","file","group","description","onProgress","onComplete","onError","showToast","File","Error","uploadRequest","progressToast","progressView","toastService","ToastService","promise","_startUpload","uploadData","result","_showProgressToast","_initiateUpload","upload_url","_performUpload","_completeUpload","warn","_onComplete","_onError","payload","file_size","content_type","response","errorMessage","set","uploadUrl","Promise","resolve","reject","xhr","XMLHttpRequest","upload","onprogress","Math","round","_onProgress","onload","statusText","onerror","ontimeout","onabort","open","setRequestHeader","timeout","send","save","setTimeout","hide","cancel","showView","autohide","dismissible","abort","then","onSuccess","catch","onFinally","finally","FileManager","FileManagerList","FileManagerForms","cols","value","text","default","owners","GroupList","UserList","credentials","isImage","get","FileList","IncidentStats","requiresId","IncidentEvent","IncidentEventList","IncidentEventForms","Incident","COMPONENTS","editable","force_top","IncidentList","IncidentRuleSet","IncidentRuleSetList","IncidentRule","IncidentRuleList","IncidentHistory","IncidentHistoryList","BundleByOptions","MatchByOptions","ComparatorOptions","ValueTypeOptions","CommonEventFields","meta","RuleSet","RuleSetList","RuleSetForms","Rule","RuleList","RuleForms","allowCustom","showDescription","EDIT_FORM","ADD_FORM","CREATE_FORM","Job","cancel_request","retry","delay","retry_request","new_job_id","newJobId","originalJobId","original_job_id","getDetailedStatus","get_status","cloneJob","newPayload","publishData","publish_job","job_id","templateJobId","template_job_id","isActive","includes","isTerminal","canRetry","canCancel","getStatusBadgeClass","pending","running","failed","canceled","expired","getStatusIcon","getEvents","getFormattedDuration","duration","toFixed","getQueuePosition","hasExpired","expiresAt","Date","getRunnerId","getPayload","getMetadata","JobList","fetchByStatus","fetch","fetchByChannel","channel","fetchPending","fetchRunning","fetchCompleted","fetchFailed","fetchScheduled","scheduled","publish","jobData","getHealth","test","tests","clearStuck","clearChannel","confirm","cleanConsumers","purgeJobs","days_old","JobLog","JobLogList","JobEvent","JobEventList","JobsEngineStats","JobRunner","runner_id","idAttribute","ping","app","getApp","toISOString","ping_status","shutdown","graceful","getChannels","isHealthy","lastHeartbeat","now","getTime","getFormattedHeartbeatAge","heartbeatAge","getUtilization","min","getFormattedUptime","started","startTime","diffMs","diffSeconds","floor","getWorkerInfo","processed","alive","utilization","getDisplayName","runnerId","parts","split","length","canControl","JobRunnerList","getActive","where","runner","getByChannel","getHealthy","getTotalProcessed","models","reduce","getTotalFailed","getSystemHealth","aliveRunners","filter","getAllChannels","channels","Set","forEach","add","Array","from","sort","window","broadcast","command","broadcastStatus","broadcastShutdown","broadcastPause","broadcastResume","broadcastReload","Log","LogList","Member","hasPermission","permission","isArray","some","p","permissions","fetchForGroup","groupId","MemberList","MemberForms","MetricsPermission","id_key","MetricsPermissionList","PushDevice","PushDeviceList","PushTemplate","PushTemplateList","PushConfig","PushConfigList","PushDelivery","PushDeliveryList","PushConfigForms","rows","PushTemplateForms","defaultParams","is_active","GeoIPForms","readonly","step","GeoLocatedIP","lookup","ip","resp","EDIT_LOCATION_FORM","EDIT_SECURITY_FORM","EDIT_NETWORK_FORM","GeoLocatedIPList","Ticket","TicketList","TicketCategories","ticket","bug","feature","incident","security","fulfillment","new_user","new_group","qa","TicketCategoriesOptions","Object","entries","map","key","TicketForms","TicketNote","TicketNoteList","TableRow","ListViewItem","tagName","className","enableTooltips","actions","contextMenu","batchActions","tableView","listView","editingCells","buildRowTemplate","getResponsiveClasses","visibility","validBreakpoints","join","classes","push","show","isSelectable","column","combinedClasses","class","c","cellContent","buildCellTemplate","cellAction","rowAction","buildActionsTemplate","buildContextMenuTemplate","path","formatter","format","icon","buildContextMenuItems","menuItem","separator","divider","itemClass","danger","onAfterRender","cell","querySelector","context","row","table","index","innerHTML","selected","classList","setAttribute","onActionEditCell","stopPropagation","columnKey","getAttribute","find","col","has","enterEditMode","onActionRowClick","target","closest","onActionView","onActionEdit","onActionDelete","cellElement","contentSpan","currentValue","editor","createCellEditor","originalContent","style","display","editorContainer","document","createElement","appendChild","input","focus","select","dataset","setupEditorEvents","originalValue","editableOptions","createSelectEditor","createSwitchEditor","createTextareaEditor","createTextEditor","inputType","escapeHtml","optionsArray","optionsHtml","option","checked","saveBtn","cancelBtn","addEventListener","e","preventDefault","saveCellEdit","cancelCellEdit","autoSave","newValue","oldValue","exitEditMode","remove","displayValue","delete","div","textContent","addClass","selectCell","deselect","removeClass","LOOKUPS","exact","in","not","not_in","gt","gte","lt","lte","contains","icontains","startswith","istartswith","endswith","iendswith","isnull","val","range","parseFilterKey","paramKey","field","possibleLookup","slice","formatFilterDisplay","lookupDef","valueStr","String","values","v","trim","formattedValues","DjangoLookups","getLookupDescription","isValidLookup","hasOwnProperty","getAvailableLookups","keys","buildFilterKey","TableView","ListView","selectionMode","selectable","emptyMessage","addButtonIcon","isFullscreen","searchable","sortable","filterable","paginated","clickAction","itemView","addForm","editForm","deleteTemplate","formDialogConfig","viewDialogOptions","exportOptions","showExport","exportSource","filters","additionalFilters","hideActivePills","hideActivePillNames","batchBarLocation","addButtonLabel","toolbarButtons","tableOptions","striped","bordered","hover","responsive","searchPlacement","searchPlaceholder","initializeColumns","extractColumnFilters","footerTotalColumns","footer_total","hasFooterTotals","buildTableTemplate","setupCollectionListeners","collection","on","updateFooterTotals","charAt","parseColumnKey","fieldKey","totals","calculateFooterTotals","totalColumnIndex","safeKey","formatValue","sum","numValue","parseFloat","originalKey","batchPanelTop","buildBatchActionsPanel","batchPanelBottom","buildToolbarTemplate","__fs","fontSize","__val","buildTableClasses","buildTableHeaderTemplate","buildTableFooterTemplate","buildPaginationTemplate","background","buildActionButtonsTemplate","buildFilterDropdownTemplate","buildSearchTemplate","buttons","isFullscreenSupported","showAdd","dropdownItems","opt","button","handler","variant","checkPermissions","iconHtml","labelHtml","dataAttrs","btnClass","buildFilterList","allFilters","getAllAvailableFilters","activeFilters","getActiveFilters","activeClass","getFilterIcon","config","updateFilterPills","container","pillsHTML","buildActivePills","updateSearchInputs","searchInputs","querySelectorAll","hasSearch","search","toString","filterEntries","getFilterLabel","headerCells","currentSort","getSortBy","getSortDirection","sortIcon","getSortIcon","responsiveClasses","footerCells","actionsHTML","batchPanelTitle","_createItemView","itemTemplate","containerId","itemViews","_onItemSelect","updateBatchActionsPanel","_onItemDeselect","_onRowClick","bind","_onRowView","_onRowEdit","_onRowDelete","_onCellEdit","_onCellSave","_onCellCancel","onMounted","setupSearchClearListener","onActionClearSearch","onRowClick","getModelClass","getModelName","MODEL_NAME","replace","getItemViewClass","VIEW_CLASS","getAddFormConfig","getEditFormConfig","getFormDialogConfig","FORM_DIALOG_CONFIG","renderTemplateString","Mustache","onItemView","ViewClass","viewInstance","Dialog","showDialog","header","body","centered","showData","onItemEdit","formConfig","showModelForm","FormView","formFields","refresh","onItemDelete","DELETE_TEMPLATE","confirmText","confirmClass","destroy","fullscreenEnabled","mozFullScreenEnabled","webkitFullscreenEnabled","msFullscreenEnabled","onActionToggleFullscreen","exitFullscreen","enterFullscreen","requestFullscreen","mozRequestFullScreen","webkitRequestFullscreen","msRequestFullscreen","updateFullscreenButton","setupFullscreenListeners","mozCancelFullScreen","webkitExitFullscreen","msExitFullscreen","_fullscreenHandler","handleFullscreenChange","fullscreenElement","mozFullScreenElement","webkitFullscreenElement","msFullscreenElement","cleanupFullscreenListeners","removeEventListener","onActionRefresh","onActionAdd","onAdd","showForm","addRequiresActiveGroup","activeGroup","addRequiresActiveUser","user","activeUser","addFormDefaults","assign","onActionExport","source","download","onExport","toJSON","onActionApplySearch","searchTerm","setFilter","start","restEnabled","startsWith","direction","onActionSort","newSort","setParams","desc","sortField","a","b","aVal","bVal","updateSortIcons","currentSortField","currentSortDir","dropdown","isSorted","dropdownMenu","nextElementSibling","ascItem","descItem","noneItem","toggle","onActionSelectAll","isCurrentlyAllSelected","every","item","clearSelection","forEachItem","selectAllCell","onBeforeRender","searchValue","footerTotals","count","end","startEl","endEl","totalEl","pageSizeSelect","renderPagination","paginationContainer","currentPage","totalPages","ceil","prevPage","nextPage","pages","visibleSet","i","visible","last","onActionPage","rawPage","parseInt","max","page","isNaN","onChangePageSize","newSize","allParams","processedKeys","filterDef","startName","endName","fieldName","filterConfig","getFilterConfig","filterKey","f","additionalFilter","getFilterDisplayValue","date","daterange","number","boolean","onActionAddFilter","buildFilterDialogField","newFilterValue","extractFilterValue","applyFilters","placeHolder","displayFormat","startDate","endDate","valueArray","formResult","filter_value","onActionEditFilter","onActionRemoveFilter","onActionClearAllFilters","selectedCount","getSelectedItems","panel","countEl","allSelected","someSelected","onActionBatch","batchAction","selectedItems","items","onActionClearSelection","onActionCustomToolbarButton","buttonIndex","call","TablePage","Page","pageName","defaultQuery","groupField","tableViewConfig","formCreate","formEdit","tableViewOptions","urlSyncEnabled","lastUpdated","isLoading","buildTemplate","onInit","applyQueryToCollection","fetchOnMount","addChild","setupEventListeners","toLocaleTimeString","updateStatusDisplay","syncUrl","handleFilterEdit","query","reservedParams","JSON","parse","force","router","currentUrl","URL","location","currentParams","searchParams","desiredParams","collectionParams","stringify","hasChanges","updateBrowserUrl","updatedElement","countElement","onEnter","requiresGroup","clearAllFilters","onGroupChange","onBeforeDestroy","off","showStatus","TabView","tabs","activeTab","tabsClass","contentClass","minWidth","enableResponsive","tabPadding","dropdownStyle","viewOptions","tabLabels","currentMode","tabWidthCache","Map","lastContainerWidth","resizeObserver","_measurementSpan","_tabComputedStyle","isMobileMode","hasOverflow","view","addTab","handleResize","renderTemplate","buildTabNavigation","buildTabContent","buildDropdownNavigation","buildTabsNavigation","tabItems","tabId","getTabId","activeLabel","buttonHtml","buildMobileDropdownNavigation","shouldUseMobileDropdown","enableMobileDropdown","viewportWidth","innerWidth","mobileBreakpoint","tabPanes","toLowerCase","showTab","tabLabel","activeView","isMounted","previousTab","updateTabNavigation","updateTabContent","activeTabLabel","previousTabLabel","reRenderNavigation","prevTabButton","activeTabButton","activeTabId","previousTabId","prevPane","activePane","onTabActivated","onActionShowTab","_initializeMeasurementStyles","tabButton","getComputedStyle","font","letterSpacing","paddingLeft","paddingRight","setupResponsiveHandling","onAfterMount","disconnect","parentElement","removeChild","getActiveTab","getTabLabels","getTab","makeActive","hasPerm","parent","removeTab","calculateTabWidth","estimatedWidth","position","whiteSpace","span","fontFamily","width","offsetWidth","getTotalTabWidth","getContainerWidth","shouldUseDropdown","containerWidth","totalTabWidth","updateNavigationMode","ResizeObserver","observe","abs","newMode","mode","navigationContainer","newNavigation","outerHTML","getNavigationMode","setNavigationMode","clearWidthCache","clear","FilePreviewView","isPdf","thumbnailUrl","onActionViewFile","LightboxGallery","MOJO","plugins","src","alt","PDFViewer","ChatMessageView","theme","isCurrentUser","getBubblesTemplate","getCompactTemplate","attachments","attachmentsContainer","filePreview","ChatInputView","buttonText","pendingUploads","enableFileDrop","dropZoneSelector","multiple","acceptedTypes","visualFeedback","dragOverClass","dragActiveClass","textarea","autoResizeTextarea","handleKeydown","shiftKey","onActionSendMessage","onFileDrop","files","uploadFile","uploadId","random","addFilePreview","updateFileProgress","uploadResult","handleUploadComplete","handleUploadError","preview","formatFileSize","progressBar","progressContainer","onActionRemoveAttachment","setBusy","busy","spinner","clearInput","height","scrollHeight","bytes","log","pow","applyFileDropMixin","ChatView","adapter","currentUserId","inputPlaceholder","inputButtonText","messages","messageViews","inputView","handleSendMessage","_buildMessageViews","_renderChildren","scrollToBottom","messagesContainer","messageView","_createMessageView","author","addMessage","scroll","addNote","requestAnimationFrame","scrollTop","clearMessages","level","example","clone"],"mappings":"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"}
|