diff --git a/docs/web/docs/guides/how_to_use/form_composer/overview.md b/docs/web/docs/guides/how_to_use/form_composer/overview.md index 5d669b10f..d60384783 100644 --- a/docs/web/docs/guides/how_to_use/form_composer/overview.md +++ b/docs/web/docs/guides/how_to_use/form_composer/overview.md @@ -16,36 +16,36 @@ You can find working demos of FormComposer in `examples/form_composer_demo` repo For details on how to run these examples, refer to the demo's [README.md](https://github.com/facebookresearch/Mephisto/blob/main/examples/form_composer_demo/README.md) -### FormComposer app UI +## FormComposer app UI -Here is how FormComposer app UI looks like. +The below screenshots showcase FormComposer app UI. -### Just started task +### Worker view - initial form -![List of tasks](./screenshots/initial_view.png) +![Initial form](./screenshots/initial_view.png)

-### Filled form +### Worker view - completed form -![List of tasks](./screenshots/in_progress_view.png) +![Completed form](./screenshots/in_progress_view.png)

-### TaskReview app +### Researcher view - Task Review main page -![List of tasks](./screenshots/review.png) +![Task Review main page](./screenshots/review.png)

-### TaskReview app. List of Units +### Researcher view - Task Review units list -![TaskReview app. List of Units](./screenshots/units_list.png) +![Task Review units list](./screenshots/units_list.png)

-### TaskReview app. Unit page +### Researcher view - Task Review unit page -![TaskReview app. Unit page](./screenshots/unit_page.png) +![Task Review unit page](./screenshots/unit_page.png)

diff --git a/docs/web/docs/guides/how_to_use/review_app/overview.md b/docs/web/docs/guides/how_to_use/review_app/overview.md index 64204daf0..e75f70ce0 100644 --- a/docs/web/docs/guides/how_to_use/review_app/overview.md +++ b/docs/web/docs/guides/how_to_use/review_app/overview.md @@ -75,3 +75,15 @@ _Note that a custom view of Task results is included (at the bottom) only if you ![Task statistics](./screenshots/task_worker_opinions.png)

+ +### Task units list + +![Task units list](./screenshots/units_list.png) +
+
+ +### Task unit page + +![Task unit page](./screenshots/unit_page.png) +
+
diff --git a/docs/web/docs/guides/how_to_use/review_app/screenshots/submission_approve_dialog.png b/docs/web/docs/guides/how_to_use/review_app/screenshots/submission_approve_dialog.png index 729a00262..2c9f6c2cd 100644 Binary files a/docs/web/docs/guides/how_to_use/review_app/screenshots/submission_approve_dialog.png and b/docs/web/docs/guides/how_to_use/review_app/screenshots/submission_approve_dialog.png differ diff --git a/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png b/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png index 7ef8b16ae..d0b66dced 100644 Binary files a/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png and b/docs/web/docs/guides/how_to_use/review_app/screenshots/tasks_list.png differ diff --git a/docs/web/docs/guides/how_to_use/review_app/screenshots/unit_page.png b/docs/web/docs/guides/how_to_use/review_app/screenshots/unit_page.png new file mode 100644 index 000000000..952bf7360 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/review_app/screenshots/unit_page.png differ diff --git a/docs/web/docs/guides/how_to_use/review_app/screenshots/units_list.png b/docs/web/docs/guides/how_to_use/review_app/screenshots/units_list.png new file mode 100644 index 000000000..d0d367876 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/review_app/screenshots/units_list.png differ diff --git a/docs/web/docs/guides/how_to_use/review_app/server_api.md b/docs/web/docs/guides/how_to_use/review_app/server_api.md index 1114d702a..07cacf002 100644 --- a/docs/web/docs/guides/how_to_use/review_app/server_api.md +++ b/docs/web/docs/guides/how_to_use/review_app/server_api.md @@ -48,6 +48,7 @@ These are the API specs enabling TaskReview app UI. Get all available tasks (to select one for review) +**Response**: ```json { "tasks": [ @@ -72,6 +73,10 @@ Get all available tasks (to select one for review) Get metadata for a task +**URL parameters**: +- `id` - id of a task + +**Response**: ```json { "id": , @@ -85,13 +90,30 @@ Get metadata for a task ### `GET /api/tasks/{id}/export-results` -Compose on the server-side a single file with reviewed task results (empty API response). +Compose on the server-side a single file with reviewed task results. + +**URL parameters**: +- `id` - id of a task + +**Response**: +```json +{ + "file_created": , +} +``` --- ### `GET /api/tasks/{id}/{n_units}/export-results.json` -Serve a single composed file with reviewed task results (API response is a file download). +Serve a single composed file with reviewed task results. + +**URL parameters**: +- `id` - id of a task +- `n_units` - amount of units. Needed to clear cached file on server and return a new one + +**Response**: +Text file with JSON --- @@ -99,6 +121,10 @@ Serve a single composed file with reviewed task results (API response is a file Assemble stats with results for a Task. +**URL parameters**: +- `id` - id of a task + +**Response**: ```json { "stats": { @@ -120,6 +146,10 @@ Assemble stats with results for a Task. Check if Grafana server is available and redirect or return error. +**URL parameters**: +- `id` - id of a task + +**Response**: ```json { "dashboard_url": | null, @@ -134,6 +164,10 @@ Check if Grafana server is available and redirect or return error. Returns all Worker Opinions related to a Task. +**URL parameters**: +- `id` - id of a task + +**Response**: ```json { "task_name": , @@ -179,6 +213,10 @@ Returns all Worker Opinions related to a Task. Get full, unpaginated list of unit IDs within a task (for subsequent client-side grouping by worker_id and `GET /task-units` pagination) +**URL parameters**: +- `id` - id of a task + +**Response**: ```json { "worker_units_ids": [ @@ -193,14 +231,20 @@ Get full, unpaginated list of unit IDs within a task (for subsequent client-side --- -### `GET /api/qualifications` +### `GET /api/qualifications?{worker_id=}` -Get all available qualifications (to select "approve" and "reject" qualifications) +Get all available qualifications (to select "approve" and "reject" qualifications). +**GET parameters**: +- `worker_id` - id of a worker, whom these qualification were granted to + +**Response**: ```json { "qualifications": [ { + "creation_date": , + "description": , "id": , "name": , }, @@ -213,28 +257,123 @@ Get all available qualifications (to select "approve" and "reject" qualification ### `POST /api/qualifications` -Create a new qualification +Create a new qualification. + +**Request**: +```json +{ + "description": , + "name": , // Required +} +``` + +**Response**: +```json +{ + "creation_date": , + "description": , + "id": , + "name": , +} +``` + +--- + +### `GET /api/qualifications/{id}` + +Get metadata for a qualificaition. + +**URL parameters**: +- `id` - id of a qualification + +**Response**: +```json +{ + "creation_date": , + "description": , + "id": , + "name": , +} +``` + +--- + +### `PATCH /api/qualifications/{id}` + +Update a qualification. +**URL parameters**: +- `id` - id of a qualification + +**Request**: ```json { + "description": , + "name": , // Required +} +``` + +**Response**: +```json +{ + "creation_date": , + "description": , + "id": , "name": , } ``` --- +### `DELETE /api/qualifications/{id}` + +Delete a qualificaition. + +**URL parameters**: +- `id` - id of a qualification + +**Response**: +```json +{} +``` + +--- + +### `GET /api/qualifications/{id}/details` + +Get additional data about a qualification. + +**URL parameters**: +- `id` - id of a qualification + +**Response**: +```json +{ + "granted_qualifications_count": , +} +``` + +--- + ### `GET /api/qualifications/{id}/workers?{task_id=}` Get list of all bearers of a qualification. +**URL parameters**: +- `id` - id of a qualification + +**GET parameters**: +- `task_id` - id of a task + +**Response**: ```json { "workers": [ { "worker_id": , "value": , - "unit_review_id": , // latest grant of this qualification - "granted_at": , // maps to `unit_review.creation_date` column + "worker_review_id": , // latest grant of this qualification + "granted_at": , // maps to `worker_review.creation_date` column }, ... // more qualified workers ] @@ -243,37 +382,137 @@ Get list of all bearers of a qualification. --- -### `POST /api/qualifications/{id}/workers/{id}/grant` +### `POST /api/qualifications/{id}/workers/{worker_id}/grant` + +Grant qualification to a worker. + +**URL parameters**: +- `id` - id of a qualification +- `worker_id` - id of a worker + +**Request**: +```json +{ + "unit_ids": [, ...], // Required + "value": , +} +``` + +**Response**: +```json +{} +``` + +--- + +### `PATCH /api/qualifications/{id}/workers/{worker_id}/grant` + +Update value of existing granted qualification. -Grant qualification to a worker +**URL parameters**: +- `id` - id of a qualification +- `worker_id` - id of a worker +**Request**: ```json { - "unit_ids": [, ...], + "explanation": , "value": , } ``` +**Response**: +```json +{} +``` + --- -### `POST /api/qualifications/{id}/workers/{id}/revoke` +### `POST /api/qualifications/{id}/workers/{worker_id}/revoke` -Revoke qualification from a worker +Revoke qualification from a worker. +**URL parameters**: +- `id` - id of a qualification +- `worker_id` - id of a worker + +**Request**: ```json { - "unit_ids": [, ...], + "unit_ids": [, ...], // Required +} +``` + +**Response**: +```json +{} +``` + +--- + +### `PATCH /api/qualifications/{id}/workers/{worker_id}/revoke` + +Revoke qualification from a worker (see the difference from `POST` in the code) + +**URL parameters**: +- `id` - id of a qualification +- `worker_id` - id of a worker + +**Response**: +```json +{} +``` + +--- + +### `GET /api/granted-qualifications?{qualification_id=}&{sort=}` + +Get list of all granted queslifications + +**GET parameters**: +- `qualification_id` - id of a qualification that was granted to a workers +- `sort` - field name and order to sort resonse results (e.g. `value_current`, `-value_current`) + +**Response**: +```json +{ + "granted_qualifications": [ + { + "granted_at": , + "qualification_id": , + "qualification_name": , + "units": [ + { + "creation_date": , + "task_id": , + "task_name": , + "unit_id": , + "value": , + }, + ... // more units + ], + "value_current": , + "worker_id": , + "worker_name": , + }, + ... // more granted qualifications + ], } ``` --- -### `GET /api/units?{task_id=}{unit_ids=}` +### `GET /api/units?{task_id=}&{unit_ids=}&{completed=}` -Get workers' results (filtered by task_id and/or unit_ids, etc) - without full details of input/output. At least one filtering parameter must be specified +Get workers' results (filtered by task_id and/or unit_ids, etc) - without full details of input/output. +At least one filtering parameter must be specified. -_NOTE: this edpoint is not currently used in TaskReview app_ +**GET parameters**: +- `task_id` - id of a task +- `unit_ids` - ids of units +- `completed` - show completed units or all (`true`/`false`) +**Response**: ```json { "units": [ @@ -289,8 +528,8 @@ _NOTE: this edpoint is not currently used in TaskReview app_ "outputs_preview": , // optional }, "review": { - "tips": , - "feedback": , + "bonus": , + "review_note": , }, "status": , "task_id": , @@ -303,8 +542,12 @@ _NOTE: this edpoint is not currently used in TaskReview app_ ### `GET /api/units/details?{unit_ids=}` -Get full input for specified workers results (`units_ids` parameter is mandatory) +Get full input for specified workers results (`units_ids` parameter is mandatory). + +**GET parameters**: +- `unit_ids` - ids of units (Required) +**Response**: ```json { "units": [ @@ -312,7 +555,7 @@ Get full input for specified workers results (`units_ids` parameter is mandatory "has_task_source_review": , "id": , "inputs": , // instructions for worker - "metadata": , // any metadata (e.g. Worker Opinion) + "metadata": , // any metadata (e.g. Worker Opinion, Unit Reviews, etc) "outputs": , // response from worker "prepared_inputs": , // prepared instructions from worker "unit_data_folder": }, // path to data dir in file system @@ -326,61 +569,123 @@ Get full input for specified workers results (`units_ids` parameter is mandatory ### `POST /api/units/approve` -Approve worker's result +Approve worker's result. +**Request**: ```json { - "unit_ids": [, ...], - "feedback": , // optional - "tips": , // optional + "unit_ids": [, ...], // Required + "review_note": , // optional + "bonus": , // optional + "send_to_worker": , // optional } ``` +**Response**: +```json +{} +``` + --- ### `POST /api/units/reject` -Reject worker's result +Reject worker's result. +**Request**: ```json { - "unit_ids": [, ...], - "feedback": , // optional + "unit_ids": [, ...], // Required + "review_note": , // optional + "send_to_worker": , // optional } ``` +**Response**: +```json +{} +``` + --- ### `POST /api/units/soft-reject` -Soft-reject worker's result +Soft-reject worker's result. +**Request**: ```json { - "unit_ids": [, ...], - "feedback": , // optional + "unit_ids": [, ...], // Required + "review_note": , // optional + "send_to_worker": , // optional } ``` +**Response**: +```json +{} +``` + --- ### `POST /api/workers/{id}/block` -Permanently block a worker +Permanently block a worker. + +**URL parameters**: +- `id` - id of a worker +**Request**: ```json { - "unit_id": , - "feedback": , + "unit_ids": [, ...], // optional + "review_note": , // Required } ``` +**Response**: +```json +{} +``` + +--- + +### `POST /api/workers/{id}/qualifications/grant` + +Grant multiple qualifications to a worker with units. + +**URL parameters**: +- `id` - id of a worker + +**Request**: +```json +{ + "unit_ids": [, ...], // Required + "qualification_grants": [ // Required + { + "qualification_id": , + "value": , + }, + ... + ], +} +``` + +**Response**: +```json +{} +``` + --- ### `GET /api/workers/{id}/qualifications` -Get list of all granted qualifications for a worker +Get list of all granted qualifications for a worker. + +**URL parameters**: +- `id` - id of a worker +**Response**: ```json { "granted_qualifications": [ @@ -388,7 +693,7 @@ Get list of all granted qualifications for a worker "worker_id": , "qualification_id": , "value": , - "granted_at": , // maps to `unit_review.creation_date` column + "granted_at": , // maps to `worker_review.creation_date` column } ], ... // more granted qualifications @@ -397,10 +702,17 @@ Get list of all granted qualifications for a worker --- -### `GET /api/review-stats?{task_id=}{worker_id=}{since=}{limit=}` +### `GET /api/review-stats?{task_id=}&{worker_id=}&{since=}&{limit=}` Get stats of (recent) approvals. Either `task_id` or `worker_id` (or both) must be present. +**GET parameters**: +- `task_id` - id of a task (Required) +- `worker_id` - id of a worker (Required) +- `since` - show stats since date or datetime +- `limit` - limit amount or items in results + +**Response**: ```json { "stats": { @@ -419,7 +731,12 @@ Get stats of (recent) approvals. Either `task_id` or `worker_id` (or both) must Return static file from `data` directory for specific unit. -Response: file. +**URL parameters**: +- `unit_id` - id of a unit +- `filename` - name of a file, that was uploaded by a worker + +**Response**: +File that was uploaded during unit completion by a worker --- diff --git a/docs/web/docs/guides/how_to_use/video_annotator/overview.md b/docs/web/docs/guides/how_to_use/video_annotator/overview.md index 1ab742403..defb1b9b9 100644 --- a/docs/web/docs/guides/how_to_use/video_annotator/overview.md +++ b/docs/web/docs/guides/how_to_use/video_annotator/overview.md @@ -8,8 +8,8 @@ sidebar_position: 1 # VideoAnnotator overview -You can easily generate tasks to annotate any preuploaded video with our VideoAnnotator feature. -It produces UI with video and instructions, where workers can create tracks with segments which they need to annotate. +You can easily generate tasks to annotate any preuploaded video with our VideoAnnotator feature. +It produces UI with video and instructions, where workers can create tracks with segments which they need to annotate. ## Live demo @@ -17,48 +17,48 @@ You can find working demos of VideoAnnotator in `examples/video_annotator_demo` For details on how to run these examples, refer to the demo's [README.md](https://github.com/facebookresearch/Mephisto/blob/main/examples/video_annotator_demo/README.md) -### VideoAnnotator app UI +## VideoAnnotator app UI -Here is how VideoAnnotator app UI looks like. +The below screenshots showcase VideoAnnotator app UI. -### Just started task +### Worker view - initial annotator -![Just started task](./screenshots/initial_view.png) +![Initial annotator](./screenshots/initial_view.png)

-### Annotated video +### Worker view - completed annotations -![Annotated video](./screenshots/in_progress_view.png) +![Completed annotations](./screenshots/in_progress_view.png)

-### TaskReview app. Collapsed +### Researcher view - Task Review unit page - collapsed sections -![TaskReview app. Collapsed](./screenshots/review_collapsed.png) +![Unit page - collapsed sections](./screenshots/review_collapsed.png)

-### TaskReview app. Open segment +### Researcher view - Task Review unit page - segment section -![TaskReview app. Open segment](./screenshots/review_open_segment.png) +![Unit page - segment section](./screenshots/review_open_segment.png)

-### TaskReview app. Open WebVTT +### Researcher view - Task Review unit page - WebVTT section -![TaskReview app. Open WebVTT](./screenshots/review_open_webvtt.png) +![Unit page - WebVTT section](./screenshots/review_open_webvtt.png)

-### TaskReview app. List of Units +### Researcher view - Task Review units list -![TaskReview app. List of Units](./screenshots/units_list.png) +![Units list](./screenshots/units_list.png)

-### TaskReview app. Unit page +### Researcher view - Task Review unit page -![TaskReview app. Unit page](./screenshots/unit_page.png) +![Unit page](./screenshots/unit_page.png)

diff --git a/docs/web/docs/guides/how_to_use/worker_quality/managing_worker_qualifications.md b/docs/web/docs/guides/how_to_use/worker_quality/managing_worker_qualifications.md new file mode 100644 index 000000000..173550a7d --- /dev/null +++ b/docs/web/docs/guides/how_to_use/worker_quality/managing_worker_qualifications.md @@ -0,0 +1,66 @@ +--- + +# Copyright (c) Meta Platforms and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +sidebar_position: 2 +--- + +# Managing worker qualifications + +You can easily manage qualification via [TaskReview app](/docs/guides/how_to_use/review_app/overview/) UI. +[This section](/docs/guides/how_to_use/review_app/running/) describes how to install and run it. + +# Worker Qualifications + +Once TaskReview app has started, on its main page you'll find a "Worker Qualifications" tab. There you can: +- view worker qualifications (i.e. qualifications assigned to your workers) +- create a new qualification +- filter and sort worker qualifications +- edit worker qualification values +- revoke worker qualification completely +- access single qualification pages +- access related task unit pages + +![Worker Qualifications tab](./screenshots/worker_qualifications_list.png) +
+
+ +# Qualification page + +Qualification page lists all workers who possess that particular qualification. There you can: + +- view and edit general info about the qualification +- delete this qualification (along with all related worker qualification records) +- edit value of each worker qualification record +- revoke this qualification from a worker completely +- access related task unit pages + +![Qualification page](./screenshots/qualification_page.png) +
+
+ +# Unit page + +Among other things, a (read-only) Unit page includes a list of all qualifications that were granted to a worker in the context of that particular Unit. + +![Unit page](./screenshots/unit_page.png) +
+
+ +# Unit review page + +While reviewing task units, you can: +- create a new qualification +- assign qualification to a worker (approve and soft-reject actions) +- write a note to yourself pertaining to the qualification assignment + +_Note that to completely revoke a pre-existing qualification from a worker, you will nee dto navigate to that Task Unit page via "Worker Qualifications" tab of the TaskReview app._ + +![Create new qualification](./screenshots/review_unit_page_create_qualification.png) +
+ +![Grant qualification](./screenshots/review_unit_page_grant_qualification.png) +
+
diff --git a/docs/web/docs/guides/how_to_use/worker_quality/other_methods.md b/docs/web/docs/guides/how_to_use/worker_quality/other_methods.md index 53e9ba524..acce0dc87 100644 --- a/docs/web/docs/guides/how_to_use/worker_quality/other_methods.md +++ b/docs/web/docs/guides/how_to_use/worker_quality/other_methods.md @@ -4,7 +4,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -sidebar_position: 5 +sidebar_position: 6 --- # Other methods for quality control diff --git a/docs/web/docs/guides/how_to_use/worker_quality/screenshots/qualification_page.png b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/qualification_page.png new file mode 100644 index 000000000..4b7d7b964 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/qualification_page.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_quality/screenshots/review_unit_page_create_qualification.png b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/review_unit_page_create_qualification.png new file mode 100644 index 000000000..46d54f0c2 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/review_unit_page_create_qualification.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_quality/screenshots/review_unit_page_grant_qualification.png b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/review_unit_page_grant_qualification.png new file mode 100644 index 000000000..7636b253a Binary files /dev/null and b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/review_unit_page_grant_qualification.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_quality/screenshots/unit_page.png b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/unit_page.png new file mode 100644 index 000000000..77001b54c Binary files /dev/null and b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/unit_page.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_quality/screenshots/worker_qualifications_list.png b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/worker_qualifications_list.png new file mode 100644 index 000000000..e7a8c35a5 Binary files /dev/null and b/docs/web/docs/guides/how_to_use/worker_quality/screenshots/worker_qualifications_list.png differ diff --git a/docs/web/docs/guides/how_to_use/worker_quality/using_golds.md b/docs/web/docs/guides/how_to_use/worker_quality/using_golds.md index b4877106f..6f64d3606 100644 --- a/docs/web/docs/guides/how_to_use/worker_quality/using_golds.md +++ b/docs/web/docs/guides/how_to_use/worker_quality/using_golds.md @@ -4,7 +4,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -sidebar_position: 4 +sidebar_position: 5 --- import Link from '@docusaurus/Link'; diff --git a/docs/web/docs/guides/how_to_use/worker_quality/using_onboarding.mdx b/docs/web/docs/guides/how_to_use/worker_quality/using_onboarding.mdx index 493768489..185fbadac 100644 --- a/docs/web/docs/guides/how_to_use/worker_quality/using_onboarding.mdx +++ b/docs/web/docs/guides/how_to_use/worker_quality/using_onboarding.mdx @@ -4,7 +4,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -sidebar_position: 2 +sidebar_position: 3 --- import ReactPlayer from "react-player"; diff --git a/docs/web/docs/guides/how_to_use/worker_quality/using_screen_units.mdx b/docs/web/docs/guides/how_to_use/worker_quality/using_screen_units.mdx index 0bd954cc9..833f25ad9 100644 --- a/docs/web/docs/guides/how_to_use/worker_quality/using_screen_units.mdx +++ b/docs/web/docs/guides/how_to_use/worker_quality/using_screen_units.mdx @@ -4,7 +4,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -sidebar_position: 3 +sidebar_position: 4 --- import ReactPlayer from "react-player"; diff --git a/mephisto/abstractions/database.py b/mephisto/abstractions/database.py index 2fa77f477..6a09f0e87 100644 --- a/mephisto/abstractions/database.py +++ b/mephisto/abstractions/database.py @@ -78,6 +78,7 @@ GET_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="get_qualification") FIND_QUALIFICATIONS_LATENCY = DATABASE_LATENCY.labels(method="find_qualifications") DELETE_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="delete_qualification") +UPDATE_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="update_qualification") GRANT_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="grant_qualification") FIND_GRANT_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="find_granted_qualification") CHECK_GRANTED_QUALIFICATIONS_LATENCY = DATABASE_LATENCY.labels( @@ -85,8 +86,8 @@ ) GET_GRANTED_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="get_granted_qualification") REVOKE_QUALIFICATION_LATENCY = DATABASE_LATENCY.labels(method="revoke_qualification") -NEW_UNIT_REVIEW_LATENCY = DATABASE_LATENCY.labels(method="new_unit_review") -UPDATE_UNIT_REVIEW_LATENCY = DATABASE_LATENCY.labels(method="update_unit_review") +NEW_WORKER_REVIEW_LATENCY = DATABASE_LATENCY.labels(method="new_worker_review") +UPDATE_WORKER_REVIEW_LATENCY = DATABASE_LATENCY.labels(method="update_worker_review") class MephistoDB(ABC): @@ -600,7 +601,8 @@ def update_unit( self, unit_id: str, agent_id: Optional[str] = None, status: Optional[str] = None ) -> None: """ - Update the given task with the given parameters if possible, raise appropriate exception otherwise. + Update the given unit with the given parameters if possible, + raise appropriate exception otherwise. """ return self._update_unit(unit_id=unit_id, status=status) @@ -901,17 +903,21 @@ def find_onboarding_agents( ) @abstractmethod - def _make_qualification(self, qualification_name: str) -> str: + def _make_qualification( + self, qualification_name: str, description: Optional[str] = None + ) -> str: """make_qualification implementation""" raise NotImplementedError() @MAKE_QUALIFICATION_LATENCY.time() - def make_qualification(self, qualification_name: str) -> str: + def make_qualification(self, qualification_name: str, description: Optional[str] = None) -> str: """ Make a new qualification, throws an error if a qualification by the given name already exists. Return the id for the qualification. """ - return self._make_qualification(qualification_name=qualification_name) + return self._make_qualification( + qualification_name=qualification_name, description=description + ) @abstractmethod def _find_qualifications(self, qualification_name: Optional[str] = None) -> List[Qualification]: @@ -942,9 +948,7 @@ def get_qualification(self, qualification_id: str) -> Mapping[str, Any]: @abstractmethod def _delete_qualification(self, qualification_name: str) -> None: - """ - Remove this qualification from all workers that have it, then delete the qualification - """ + """delete_qualification implementation""" raise NotImplementedError() @DELETE_QUALIFICATION_LATENCY.time() @@ -958,16 +962,46 @@ def delete_qualification(self, qualification_name: str) -> None: provider = ProviderClass(self) provider.cleanup_qualification(qualification_name) + @abstractmethod + def _update_qualification( + self, + qualification_id: str, + name: str, + description: Optional[str] = None, + ) -> None: + """update_qualification implementation""" + raise NotImplementedError() + + @UPDATE_QUALIFICATION_LATENCY.time() + def update_qualification( + self, + qualification_id: str, + name: str, + description: Optional[str] = None, + ) -> None: + """ + Update the given qualification with the given parameters if possible, + raise appropriate exception otherwise. + """ + return self._update_qualification( + qualification_id=qualification_id, + name=name, + description=description, + ) + @FIND_GRANT_QUALIFICATION_LATENCY.time() def find_granted_qualifications( self, worker_id: Optional[str] = None, + qualification_id: Optional[str] = None, ) -> List[GrantedQualification]: """ Find granted qualifications. - If `worker_id` is not supplied, returns all granted qualifications. + If nothing supplied, returns all granted qualifications. """ - return self._check_granted_qualifications(worker_id=worker_id) + return self._check_granted_qualifications( + worker_id=worker_id, qualification_id=qualification_id + ) @abstractmethod def _grant_qualification(self, qualification_id: str, worker_id: str, value: int = 1) -> None: @@ -1040,57 +1074,79 @@ def revoke_qualification(self, qualification_id: str, worker_id: str) -> None: """ return self._revoke_qualification(qualification_id=qualification_id, worker_id=worker_id) - def _new_unit_review( + def _new_worker_review( self, - unit_id: Union[int, str], - task_id: Union[int, str], worker_id: Union[int, str], - status: str, + status: Optional[str] = None, + task_id: Optional[Union[int, str]] = None, + unit_id: Optional[Union[int, str]] = None, + qualification_id: Optional[Union[int, str]] = None, + value: Optional[int] = None, review_note: Optional[str] = None, bonus: Optional[str] = None, + revoke: bool = False, ) -> None: - """new_unit_review implementation""" + """new_worker_review implementation""" raise NotImplementedError() - @NEW_UNIT_REVIEW_LATENCY.time() - def new_unit_review( + @NEW_WORKER_REVIEW_LATENCY.time() + def new_worker_review( self, - unit_id: Union[int, str], - task_id: Union[int, str], worker_id: Union[int, str], - status: str, + status: Optional[str] = None, + task_id: Optional[Union[int, str]] = None, + unit_id: Optional[Union[int, str]] = None, + qualification_id: Optional[Union[int, str]] = None, + value: Optional[int] = None, review_note: Optional[str] = None, bonus: Optional[str] = None, + revoke: bool = False, ) -> None: - """Create unit review""" - return self._new_unit_review(unit_id, task_id, worker_id, status, review_note, bonus) + """Create worker review""" + return self._new_worker_review( + worker_id=worker_id, + status=status, + task_id=task_id, + unit_id=unit_id, + qualification_id=qualification_id, + value=value, + review_note=review_note, + bonus=bonus, + revoke=revoke, + ) @abstractmethod - def _update_unit_review( + def _update_worker_review( self, - unit_id: int, - qualification_id: int, - worker_id: int, + unit_id: Union[int, str], + qualification_id: Union[int, str], + worker_id: Union[int, str], value: Optional[int] = None, revoke: bool = False, ) -> None: - """update_unit_review implementation""" + """update_worker_review implementation""" raise NotImplementedError() - @UPDATE_UNIT_REVIEW_LATENCY.time() - def update_unit_review( + @UPDATE_WORKER_REVIEW_LATENCY.time() + def update_worker_review( self, - unit_id: int, - qualification_id: int, - worker_id: int, + unit_id: Union[int, str], + qualification_id: Union[int, str], + worker_id: Union[int, str], value: Optional[int] = None, revoke: bool = False, ) -> None: """ - Update the given unit review with the given parameters if possible, + Update the given worker review with the given parameters if possible, raise appropriate exception otherwise. """ - return self._update_unit_review(unit_id, qualification_id, worker_id, value, revoke) + return self._update_worker_review( + unit_id=unit_id, + qualification_id=qualification_id, + worker_id=worker_id, + value=value, + revoke=revoke, + ) # File/blob manipulation methods diff --git a/mephisto/abstractions/databases/local_database.py b/mephisto/abstractions/databases/local_database.py index bfca8f822..9bb7e05d9 100644 --- a/mephisto/abstractions/databases/local_database.py +++ b/mephisto/abstractions/databases/local_database.py @@ -46,7 +46,7 @@ logger = get_logger(name=__name__) -def nonesafe_int(in_string: Optional[str]) -> Optional[int]: +def nonesafe_int(in_string: Optional[Union[str, int]]) -> Optional[int]: """Cast input to an int or None""" if in_string is None: return None @@ -784,7 +784,7 @@ def _update_unit( self, unit_id: str, agent_id: Optional[str] = None, status: Optional[str] = None ) -> None: """ - Update the given task with the given parameters if possible, + Update the given unit with the given parameters if possible, raise appropriate exception otherwise. """ if status not in AssignmentState.valid_unit(): @@ -1117,7 +1117,9 @@ def _find_agents( rows = c.fetchall() return [Agent(self, str(r["agent_id"]), row=r, _used_new_call=True) for r in rows] - def _make_qualification(self, qualification_name: str) -> str: + def _make_qualification( + self, qualification_name: str, description: Optional[str] = None + ) -> str: """ Make a new qualification, throws an error if a qualification by the given name already exists. Return the id for the qualification. @@ -1128,8 +1130,8 @@ def _make_qualification(self, qualification_name: str) -> str: c = conn.cursor() try: c.execute( - "INSERT INTO qualifications(qualification_name) VALUES (?);", - (qualification_name,), + "INSERT INTO qualifications(qualification_name, description) VALUES (?, ?);", + (qualification_name, description), ) qualification_id = str(c.lastrowid) return qualification_id @@ -1195,6 +1197,38 @@ def _delete_qualification(self, qualification_name: str) -> None: (qualification_name,), ) + def _update_qualification( + self, + qualification_id: str, + name: str, + description: Optional[str] = None, + ) -> None: + """ + Update the given qualification with the given parameters if possible, + raise appropriate exception otherwise. + """ + with self.table_access_condition, self.get_connection() as conn: + c = conn.cursor() + try: + c.execute( + """ + UPDATE qualifications + SET qualification_name = ?2, description = ?3 + WHERE qualification_id = ?1; + """, + [ + nonesafe_int(qualification_id), + name, + description, + ], + ) + except sqlite3.IntegrityError as e: + if is_key_failure(e): + raise EntryDoesNotExistException( + f"Given qualification_id {qualification_id} not found in the database" + ) + raise MephistoDBException(e) + def _grant_qualification(self, qualification_id: str, worker_id: str, value: int = 1) -> None: """ Grant a worker the given qualification. Update the qualification value if it @@ -1438,16 +1472,19 @@ def _find_onboarding_agents( ] @retry_generate_id(caught_excs=[EntryAlreadyExistsException]) - def _new_unit_review( + def _new_worker_review( self, - unit_id: Union[int, str], - task_id: Union[int, str], worker_id: Union[int, str], - status: str, + status: Optional[str] = None, + task_id: Optional[Union[int, str]] = None, + unit_id: Optional[Union[int, str]] = None, + qualification_id: Optional[Union[int, str]] = None, + value: Optional[int] = None, review_note: Optional[str] = None, bonus: Optional[str] = None, + revoke: bool = False, ) -> None: - """Create unit review""" + """Create worker review""" with self.table_access_condition: conn = self.get_connection() @@ -1455,25 +1492,31 @@ def _new_unit_review( try: c.execute( """ - INSERT INTO unit_review ( + INSERT INTO worker_review ( id, unit_id, worker_id, task_id, + updated_qualification_id, + updated_qualification_value, + revoked_qualification_id, status, review_note, bonus - ) VALUES (?, ?, ?, ?, ?, ?, ?); + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); """, - ( + [ make_randomized_int_id(), nonesafe_int(unit_id), nonesafe_int(worker_id), nonesafe_int(task_id), + nonesafe_int(qualification_id) if not revoke else None, + value, + nonesafe_int(qualification_id) if revoke else None, status, review_note, bonus, - ), + ], ) conn.commit() except sqlite3.IntegrityError as e: @@ -1481,21 +1524,21 @@ def _new_unit_review( raise EntryAlreadyExistsException( e, db=self, - table_name="unit_review", + table_name="worker_review", original_exc=e, ) raise MephistoDBException(e) - def _update_unit_review( + def _update_worker_review( self, - unit_id: int, - qualification_id: int, - worker_id: int, + unit_id: Union[int, str], + qualification_id: Union[int, str], + worker_id: Union[int, str], value: Optional[int] = None, revoke: bool = False, ) -> None: """ - Update the given unit review with the given parameters if possible, + Update the given worker review with the given parameters if possible, raise appropriate exception otherwise. """ with self.table_access_condition: @@ -1504,37 +1547,62 @@ def _update_unit_review( c.execute( """ - SELECT * FROM unit_review + SELECT * FROM worker_review WHERE (unit_id = ?) AND (worker_id = ?) ORDER BY creation_date ASC; """, - (unit_id, worker_id), + [ + nonesafe_int(unit_id), + nonesafe_int(worker_id), + ], ) results = c.fetchall() if not results: raise EntryDoesNotExistException( - f"`unit_review` was not created for this `unit_id={unit_id}`" + f"`worker_review` was not created for this `unit_id={unit_id}`" ) - latest_unit_review_id = results[-1]["id"] + latest_worker_review = results[-1] + latest_worker_review_id = latest_worker_review["id"] - c.execute( - """ - UPDATE unit_review - SET - updated_qualification_id = ?, - updated_qualification_value = ?, - revoked_qualification_id = ? - WHERE id = ?; - """, - ( - qualification_id if not revoke else None, - value, - qualification_id if revoke else None, - latest_unit_review_id, - ), + has_entry_with_qualification = ( + latest_worker_review["updated_qualification_id"] + or latest_worker_review["revoked_qualification_id"] ) - conn.commit() + + if not has_entry_with_qualification: + # Update just created entry when unit was approved + c.execute( + """ + UPDATE worker_review + SET + updated_qualification_id = ?, + updated_qualification_value = ?, + revoked_qualification_id = ? + WHERE id = ?; + """, + ( + nonesafe_int(qualification_id) if not revoke else None, + value, + nonesafe_int(qualification_id) if revoke else None, + nonesafe_int(latest_worker_review_id), + ), + ) + conn.commit() + else: + # If we try to update entry for the same unit, + # but it was already assigned to another qualifications, + # create a new entry with the same data, but with another qualification and value + self._new_worker_review( + worker_id=worker_id, + status=latest_worker_review["status"], + task_id=latest_worker_review["task_id"], + unit_id=unit_id, + qualification_id=qualification_id, + value=value, + review_note=latest_worker_review["review_note"], + bonus=latest_worker_review["bonus"], + ) # File/blob manipulation methods diff --git a/mephisto/abstractions/databases/local_database_tables.py b/mephisto/abstractions/databases/local_database_tables.py index ceeccb9ea..f55868085 100644 --- a/mephisto/abstractions/databases/local_database_tables.py +++ b/mephisto/abstractions/databases/local_database_tables.py @@ -219,5 +219,5 @@ CREATE INDEX IF NOT EXISTS assignment_by_task_run_index ON assignments(task_run_id); CREATE INDEX IF NOT EXISTS task_run_by_requester_index ON task_runs(requester_id); CREATE INDEX IF NOT EXISTS task_run_by_task_index ON task_runs(task_id); - CREATE INDEX IF NOT EXISTS unit_review_by_unit_index ON unit_review(unit_id); + CREATE INDEX IF NOT EXISTS worker_review_by_unit_index ON worker_review(unit_id); """ # noqa: E501 diff --git a/mephisto/abstractions/databases/migrations/_002_20241002_modify_qualifications.py b/mephisto/abstractions/databases/migrations/_002_20241002_modify_qualifications.py new file mode 100644 index 000000000..b666fec20 --- /dev/null +++ b/mephisto/abstractions/databases/migrations/_002_20241002_modify_qualifications.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# Copyright (c) Meta Platforms and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +""" +List of changes: +1. Add `description` field into `qualifications` table +2. Rename `unit_review` table into `worker_review` and + make unit-related fields nullable in table `unit_review` +""" + + +MODIFY_QUALIFICATIONS = """ + /* 1. Add `description` field into `qualifications` table */ + ALTER TABLE qualifications ADD COLUMN description CHAR(500); + + /* Disable FK constraints */ + PRAGMA foreign_keys = off; + + /* 2. Rename `unit_review` table into `worker_review` and + make unit-related fields nullable in table `unit_review` */ + CREATE TABLE IF NOT EXISTS worker_review ( + id INTEGER PRIMARY KEY, + unit_id INTEGER, + worker_id INTEGER NOT NULL, + task_id INTEGER, + status TEXT, + review_note TEXT, + bonus INTEGER, + blocked_worker BOOLEAN DEFAULT false, + /* ID of `db.qualifications` (not `db.granted_qualifications`) */ + updated_qualification_id INTEGER, + updated_qualification_value INTEGER, + /* ID of `db.qualifications` (not `db.granted_qualifications`) */ + revoked_qualification_id INTEGER, + creation_date DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), + + FOREIGN KEY (unit_id) REFERENCES units (unit_id), + FOREIGN KEY (worker_id) REFERENCES workers (worker_id), + FOREIGN KEY (task_id) REFERENCES tasks (task_id) + ); + INSERT INTO worker_review SELECT * FROM unit_review; + DROP INDEX IF EXISTS unit_review_by_unit_index; + DROP TABLE IF EXISTS unit_review; + + /* Enable FK constraints back */ + PRAGMA foreign_keys = on; +""" diff --git a/mephisto/abstractions/databases/migrations/__init__.py b/mephisto/abstractions/databases/migrations/__init__.py index 5f1d516d6..78c496cde 100644 --- a/mephisto/abstractions/databases/migrations/__init__.py +++ b/mephisto/abstractions/databases/migrations/__init__.py @@ -5,8 +5,10 @@ # LICENSE file in the root directory of this source tree. from ._001_20240325_data_porter_feature import * +from ._002_20241002_modify_qualifications import * migrations = { "20240418_data_porter_feature": MODIFICATIONS_FOR_DATA_PORTER, + "20241002_modify_qualifications": MODIFY_QUALIFICATIONS, } diff --git a/mephisto/abstractions/providers/inhouse/inhouse_agent.py b/mephisto/abstractions/providers/inhouse/inhouse_agent.py index a924c3679..32c0d5621 100644 --- a/mephisto/abstractions/providers/inhouse/inhouse_agent.py +++ b/mephisto/abstractions/providers/inhouse/inhouse_agent.py @@ -77,7 +77,7 @@ def approve_work( self, review_note: Optional[str] = None, bonus: Optional[Union[int, float]] = None, - skip_unit_review: bool = False, + skip_worker_review: bool = False, ) -> None: """Approve the work done on this specific Unit""" logger.debug(f"{self.log_prefix}Approving work") @@ -94,14 +94,14 @@ def approve_work( self.update_status(AgentState.STATUS_APPROVED) - if not skip_unit_review: - self.db.new_unit_review( + if not skip_worker_review: + self.db.new_worker_review( unit_id=self.unit.db_id, task_id=self.unit.task_id, worker_id=self.unit.worker_id, status=AgentState.STATUS_APPROVED, review_note=review_note, - bonus=str(bonus), + bonus=bonus and str(bonus), ) def soft_reject_work(self, review_note: Optional[str] = None) -> None: @@ -129,7 +129,7 @@ def reject_work(self, review_note: Optional[str] = None) -> None: self.update_status(AgentState.STATUS_REJECTED) - self.db.new_unit_review( + self.db.new_worker_review( unit_id=self.unit.db_id, task_id=self.unit.task_id, worker_id=self.unit.worker_id, diff --git a/mephisto/abstractions/providers/mock/mock_agent.py b/mephisto/abstractions/providers/mock/mock_agent.py index fc955b8b0..e82108e27 100644 --- a/mephisto/abstractions/providers/mock/mock_agent.py +++ b/mephisto/abstractions/providers/mock/mock_agent.py @@ -81,7 +81,7 @@ def approve_work( self, review_note: Optional[str] = None, bonus: Optional[Union[int, float]] = None, - skip_unit_review: bool = False, + skip_worker_review: bool = False, ) -> None: """ Approve the work done on this specific Unit @@ -90,9 +90,9 @@ def approve_work( """ self.update_status(AgentState.STATUS_APPROVED) - if not skip_unit_review: + if not skip_worker_review: unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, @@ -108,7 +108,7 @@ def reject_work(self, review_note: Optional[str] = None) -> None: self.update_status(AgentState.STATUS_REJECTED) unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, diff --git a/mephisto/abstractions/providers/mturk/mturk_agent.py b/mephisto/abstractions/providers/mturk/mturk_agent.py index 72d3eac86..f4283f4ea 100644 --- a/mephisto/abstractions/providers/mturk/mturk_agent.py +++ b/mephisto/abstractions/providers/mturk/mturk_agent.py @@ -107,7 +107,7 @@ def approve_work( self, review_note: Optional[str] = None, bonus: Optional[Union[int, float]] = None, - skip_unit_review: bool = False, + skip_worker_review: bool = False, ) -> None: """Approve the work done on this specific Unit""" if self.get_status() == AgentState.STATUS_APPROVED: @@ -117,9 +117,9 @@ def approve_work( approve_work(client, self._get_mturk_assignment_id(), override_rejection=True) self.update_status(AgentState.STATUS_APPROVED) - if not skip_unit_review: + if not skip_worker_review: unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, @@ -138,7 +138,7 @@ def reject_work(self, review_note: Optional[str] = None) -> None: self.update_status(AgentState.STATUS_REJECTED) unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, diff --git a/mephisto/abstractions/providers/prolific/prolific_agent.py b/mephisto/abstractions/providers/prolific/prolific_agent.py index f6e7b7ca8..dc491accb 100644 --- a/mephisto/abstractions/providers/prolific/prolific_agent.py +++ b/mephisto/abstractions/providers/prolific/prolific_agent.py @@ -104,7 +104,7 @@ def approve_work( self, review_note: Optional[str] = None, bonus: Optional[Union[int, float]] = None, - skip_unit_review: bool = False, + skip_worker_review: bool = False, ) -> None: """Approve the work done on this specific Unit""" logger.debug(f"{self.log_prefix}Approving work") @@ -131,15 +131,15 @@ def approve_work( self.update_status(AgentState.STATUS_APPROVED) - if not skip_unit_review: + if not skip_worker_review: unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, status=AgentState.STATUS_APPROVED, review_note=review_note, - bonus=str(bonus), + bonus=bonus and str(bonus), ) def soft_reject_work(self, review_note: Optional[str] = None) -> None: @@ -198,7 +198,7 @@ def reject_work(self, review_note: Optional[str] = None) -> None: self.update_status(AgentState.STATUS_REJECTED) unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, diff --git a/mephisto/data_model/agent.py b/mephisto/data_model/agent.py index bf780803d..fc53a9632 100644 --- a/mephisto/data_model/agent.py +++ b/mephisto/data_model/agent.py @@ -506,7 +506,7 @@ def approve_work( self, review_note: Optional[str] = None, bonus: Optional[Union[int, float]] = None, - skip_unit_review: bool = False, + skip_worker_review: bool = False, ) -> None: """Approve the work done on this agent's specific Unit""" raise NotImplementedError() @@ -520,11 +520,11 @@ def soft_reject_work(self, review_note: Optional[str] = None) -> None: # qualification automatically if a threshold of # soft rejects as a proportion of total accepts # is exceeded - self.approve_work(review_note=review_note, skip_unit_review=True) + self.approve_work(review_note=review_note, skip_worker_review=True) self.update_status(AgentState.STATUS_SOFT_REJECTED) unit = self.get_unit() - self.db.new_unit_review( + self.db.new_worker_review( unit_id=unit.db_id, task_id=unit.task_id, worker_id=unit.worker_id, diff --git a/mephisto/data_model/qualification.py b/mephisto/data_model/qualification.py index d05eff0af..8cb617c60 100644 --- a/mephisto/data_model/qualification.py +++ b/mephisto/data_model/qualification.py @@ -4,18 +4,18 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -from mephisto.data_model._db_backed_meta import ( - MephistoDBBackedMeta, - MephistoDataModelComponentMixin, -) - -from typing import Optional, Mapping, TYPE_CHECKING, Any +from typing import Any +from typing import Mapping +from typing import Optional +from typing import TYPE_CHECKING +from mephisto.data_model._db_backed_meta import MephistoDataModelComponentMixin +from mephisto.data_model._db_backed_meta import MephistoDBBackedMeta +from mephisto.utils.logger_core import get_logger if TYPE_CHECKING: from mephisto.abstractions.database import MephistoDB -from mephisto.utils.logger_core import get_logger logger = get_logger(name=__name__) @@ -72,11 +72,16 @@ def __init__( "now deprecated in favor of calling Qualification.get(db, id). " ) self.db: "MephistoDB" = db + if row is None: row = db.get_qualification(db_id) + assert row is not None, f"Given db_id {db_id} did not exist in given db" + self.db_id: str = row["qualification_id"] self.qualification_name: str = row["qualification_name"] + self.description: str = row["description"] + self.creation_date: str = row["creation_date"] class GrantedQualification: @@ -90,9 +95,14 @@ def __init__( row: Optional[Mapping[str, Any]] = None, ): self.db: "MephistoDB" = db + if row is None: row = db.get_granted_qualification(qualification_id, worker_id) + assert row is not None, f"Granted qualification did not exist in given db" + self.worker_id: str = row["worker_id"] self.qualification_id: str = row["qualification_id"] self.value: str = row["value"] + self.creation_date: str = row["creation_date"] + self.update_date: str = row["update_date"] diff --git a/mephisto/data_model/worker.py b/mephisto/data_model/worker.py index 3caed2d1a..d308f7c40 100644 --- a/mephisto/data_model/worker.py +++ b/mephisto/data_model/worker.py @@ -171,7 +171,11 @@ def is_qualified(self, qualification_name: str): return False return bool(qualification.value) - def revoke_qualification(self, qualification_name) -> bool: + def revoke_qualification( + self, + qualification_name: str, + skip_crowd: Optional[bool] = False, + ) -> bool: """ Remove this user's qualification if it exists @@ -183,19 +187,29 @@ def revoke_qualification(self, qualification_name) -> bool: return False logger.debug(f"Revoking qualification {qualification_name} from worker {self}.") + + # Revoke local qualification self.db.revoke_qualification(granted_qualification.qualification_id, self.db_id) - try: - self.revoke_crowd_qualification(qualification_name) - return True - except Exception as e: - logger.exception( - f"Found error while trying to revoke qualification: {repr(e)}", - exc_info=True, - ) - return False + + # Revoke crowd qualification + if not skip_crowd: + try: + self.revoke_crowd_qualification(qualification_name) + except Exception as e: + logger.exception( + f"Found error while trying to revoke qualification: {repr(e)}", + exc_info=True, + ) + return False + return True - def grant_qualification(self, qualification_name: str, value: int = 1, skip_crowd=False): + def grant_qualification( + self, + qualification_name: str, + value: int = 1, + skip_crowd: Optional[bool] = False, + ) -> bool: """ Grant a positive or negative qualification to this worker @@ -207,12 +221,15 @@ def grant_qualification(self, qualification_name: str, value: int = 1, skip_crow raise Exception(f"No qualification by the name {qualification_name} found in the db") logger.debug(f"Granting worker {self} qualification {qualification_name}: {value}") + + # Grant local qualification qualification = found_qualifications[0] self.db.grant_qualification(qualification.db_id, self.db_id, value=value) + + # Grant crowd qualification if not skip_crowd: try: self.grant_crowd_qualification(qualification_name, value) - return True except Exception as e: logger.exception( f"Found error while trying to grant qualification: {repr(e)}", @@ -220,6 +237,8 @@ def grant_qualification(self, qualification_name: str, value: int = 1, skip_crow ) return False + return True + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.db_id})" diff --git a/mephisto/review_app/client/package-lock.json b/mephisto/review_app/client/package-lock.json index 9cf63450c..63b78eced 100644 --- a/mephisto/review_app/client/package-lock.json +++ b/mephisto/review_app/client/package-lock.json @@ -12,6 +12,7 @@ "@types/react-dom": "^18.2.7", "bootstrap": "^5.3.1", "d3": "^7.9.0", + "jquery": "^3.6.0", "lodash": "^4.17.21", "mephisto-task": "^2.0.4", "moment": "^2.29.4", @@ -11764,6 +11765,12 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -26453,6 +26460,11 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==" }, + "jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/mephisto/review_app/client/package.json b/mephisto/review_app/client/package.json index cf0c54d7d..d95ad0cbb 100644 --- a/mephisto/review_app/client/package.json +++ b/mephisto/review_app/client/package.json @@ -7,6 +7,7 @@ "@types/react-dom": "^18.2.7", "bootstrap": "^5.3.1", "d3": "^7.9.0", + "jquery": "^3.6.0", "lodash": "^4.17.21", "mephisto-task": "^2.0.4", "moment": "^2.29.4", diff --git a/mephisto/review_app/client/public/index.html b/mephisto/review_app/client/public/index.html index c9a17fe59..ec78d385f 100644 --- a/mephisto/review_app/client/public/index.html +++ b/mephisto/review_app/client/public/index.html @@ -12,6 +12,10 @@ Review + diff --git a/mephisto/review_app/client/src/App/App.tsx b/mephisto/review_app/client/src/App/App.tsx index de5bc225b..bd6c5ea41 100644 --- a/mephisto/review_app/client/src/App/App.tsx +++ b/mephisto/review_app/client/src/App/App.tsx @@ -8,6 +8,7 @@ import "bootstrap/dist/css/bootstrap.min.css"; import "bootstrap/dist/js/bootstrap.bundle.min"; import Errors from "components/Errors/Errors"; import HomePage from "pages/HomePage/HomePage"; +import QualificationPage from "pages/QualificationPage/QualificationPage"; import TaskPage from "pages/TaskPage/TaskPage"; import TasksPage from "pages/TasksPage/TasksPage"; import TaskStatsPage from "pages/TaskStatsPage/TaskStatsPage"; @@ -58,6 +59,10 @@ function App() { path={urls.client.tasks} element={} /> + } + /> diff --git a/mephisto/review_app/client/src/components/ColumnTitleWithSort/ColumnTitleWithSort.css b/mephisto/review_app/client/src/components/ColumnTitleWithSort/ColumnTitleWithSort.css new file mode 100644 index 000000000..78869800a --- /dev/null +++ b/mephisto/review_app/client/src/components/ColumnTitleWithSort/ColumnTitleWithSort.css @@ -0,0 +1,33 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.column-title-with-sort { + display: inline-flex; + align-items: center; + cursor: pointer; +} + +.sort-arrows { + display: inline-flex; + flex-direction: column; + margin-left: 4px; + width: 10px; +} + +.sort-arrows i { + font-style: initial; +} + +.sort-arrows.inactive { + line-height: 0.4; + font-size: 20px; +} + +.sort-arrows.asc, +.sort-arrows.desc { + line-height: 1; + font-size: 13px; +} diff --git a/mephisto/review_app/client/src/components/ColumnTitleWithSort/ColumnTitleWithSort.tsx b/mephisto/review_app/client/src/components/ColumnTitleWithSort/ColumnTitleWithSort.tsx new file mode 100644 index 000000000..6855737d8 --- /dev/null +++ b/mephisto/review_app/client/src/components/ColumnTitleWithSort/ColumnTitleWithSort.tsx @@ -0,0 +1,84 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useEffect } from "react"; +import * as React from "react"; +import "./ColumnTitleWithSort.css"; + +export enum SortArrowsState { + ASC = "asc", + DESC = "desc", + INACTIVE = "inactive", +} + +const NextStateMapping = { + [SortArrowsState.INACTIVE]: SortArrowsState.ASC, + [SortArrowsState.ASC]: SortArrowsState.DESC, + [SortArrowsState.DESC]: SortArrowsState.INACTIVE, +}; + +type SortArrowsPropsType = { + state: SortArrowsState; +}; + +function SortArrows(props: SortArrowsPropsType) { + return ( +
+ {props.state === SortArrowsState.INACTIVE && ( + <> + + + + )} + + {props.state === SortArrowsState.ASC && ( + + )} + + {props.state === SortArrowsState.DESC && ( + + )} +
+ ); +} + +type ColumnTitleWithSortPropsType = { + onClick?: Function; + state?: SortArrowsState; + title: string | React.ReactElement; +}; + +function ColumnTitleWithSort(props: ColumnTitleWithSortPropsType) { + const [state, setState] = React.useState( + props.state || SortArrowsState.INACTIVE + ); + + // Methods + + function onClick() { + let newState = NextStateMapping[state]; + props.onClick && props.onClick(newState); + setState(newState); + } + + // Effects + + useEffect(() => { + if (props.state !== state) { + setState(props.state); + } + }, [props.state]); + + return ( +
+ {props.title} + + +
+ ); +} + +export default ColumnTitleWithSort; diff --git a/mephisto/review_app/client/src/components/EditGrantedQualificationModal/EditGrantedQualificationModal.css b/mephisto/review_app/client/src/components/EditGrantedQualificationModal/EditGrantedQualificationModal.css new file mode 100644 index 000000000..bc53e3942 --- /dev/null +++ b/mephisto/review_app/client/src/components/EditGrantedQualificationModal/EditGrantedQualificationModal.css @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.edit-granted-qualification-modal { +} + +.edit-granted-qualification-modal .modal-dialog .modal-header { + background-color: #ecdadf; + display: flex; + justify-content: center; + padding: 5px; + border-radius: 0; +} + +.edit-granted-qualification-modal .modal-dialog .modal-header .modal-title { + font-size: 26px; +} + +.edit-granted-qualification-modal .modal-dialog .modal-header .btn-close { + position: absolute; + right: 14px; +} + +.edit-granted-qualification-modal .modal-dialog .modal-content { + border-radius: initial; +} + +.edit-granted-qualification-modal .edit-granted-qualification-form { +} + +.edit-granted-qualification-modal .edit-granted-qualification-form > * input, +.edit-granted-qualification-modal + .edit-granted-qualification-form + > * + textarea { + border: 1px solid black; +} + +.edit-granted-qualification-modal + .modal-dialog + .modal-content + .modal-footer + .edit-granted-qualification-buttons { + width: 100%; + display: flex; + justify-content: space-between; +} + +.edit-granted-qualification-modal + .modal-dialog + .modal-content + .modal-footer + .edit-granted-qualification-buttons + .right-buttons { + display: flex; + gap: 4px; +} diff --git a/mephisto/review_app/client/src/components/EditGrantedQualificationModal/EditGrantedQualificationModal.tsx b/mephisto/review_app/client/src/components/EditGrantedQualificationModal/EditGrantedQualificationModal.tsx new file mode 100644 index 000000000..2f4c04dd0 --- /dev/null +++ b/mephisto/review_app/client/src/components/EditGrantedQualificationModal/EditGrantedQualificationModal.tsx @@ -0,0 +1,190 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + EDIT_GRANTED_QUALIFICATION_EXPLANATION_LENGTH, + EDIT_GRANTED_QUALIFICATION_VALUE_LENGTH, +} from "consts/review"; +import cloneDeep from "lodash/cloneDeep"; +import * as React from "react"; +import { useEffect } from "react"; +import { Button, Col, Form, Modal, Row } from "react-bootstrap"; +import "./EditGrantedQualificationModal.css"; + +export type EditGrantedQualificationFormType = { + value: number; + explanation?: string; +}; + +type EditGrantedQualificationModalPropsType = { + grantedQualification: FullGrantedQualificationType; + onRevoke: Function; + onSubmit: Function; + setErrors: Function; + setShow: React.Dispatch>; + show: boolean; +}; + +function EditGrantedQualificationModal( + props: EditGrantedQualificationModalPropsType +) { + const defaultFormValue = { + value: props.grantedQualification?.value_current || 0, + }; + + const [form, setForm] = React.useState( + cloneDeep(defaultFormValue) + ); + const [formIsValid, setFormIsValid] = React.useState(false); + const [valueHasChanged, setValueHasChanged] = React.useState(false); + + const revokeButtonDisabled = valueHasChanged; + const saveButtonDisabled = !valueHasChanged || !formIsValid; + + // Methods + + function onModalClose() { + props.setShow(!props.show); + } + + function updateForm(fieldName: string, value: string) { + if (fieldName === "value") { + const re = /^[0-9\b]+$/; + // if value is not blank, then test the regex + if (value === "" || re.test(value)) { + setForm({ ...form, [fieldName]: parseInt(value) }); + } + setValueHasChanged(true); + } else { + setForm({ ...form, [fieldName]: value }); + } + } + + // Effects + + useEffect(() => { + if (String(form.value) !== "") { + setFormIsValid(true); + } else { + setFormIsValid(false); + } + }, [form]); + + useEffect(() => { + if (props.show) { + setForm(cloneDeep(defaultFormValue)); + setValueHasChanged(false); + } + }, [props.show]); + + return ( + props.show && ( + + + Edit Worker Qualification + + + +
{ + e.preventDefault(); + }} + > + + + Qualification value + + + + updateForm("value", e.target.value)} + /> + + + + + + Explanation + + + + updateForm("explanation", e.target.value)} + /> + + +
+
+ + +
+ + +
+ + + +
+
+
+
+ ) + ); +} + +export default EditGrantedQualificationModal; diff --git a/mephisto/review_app/client/src/components/Preloader/Preloader.css b/mephisto/review_app/client/src/components/Preloader/Preloader.css new file mode 100644 index 000000000..bbce0b887 --- /dev/null +++ b/mephisto/review_app/client/src/components/Preloader/Preloader.css @@ -0,0 +1,13 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.loading { + width: 100%; + height: 100px; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/mephisto/review_app/client/src/components/Preloader/Preloader.tsx b/mephisto/review_app/client/src/components/Preloader/Preloader.tsx new file mode 100644 index 000000000..22122c3b3 --- /dev/null +++ b/mephisto/review_app/client/src/components/Preloader/Preloader.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import { Spinner } from "react-bootstrap"; +import "./Preloader.css"; + +type PreloaderPropsType = { + className?: string; + loading: boolean; +}; + +function Preloader(props: PreloaderPropsType) { + if (!props.loading) { + return null; + } + + return ( +
+ + Loading... + +
+ ); +} + +export default Preloader; diff --git a/mephisto/review_app/client/src/components/Tabs/Tabs.css b/mephisto/review_app/client/src/components/Tabs/Tabs.css new file mode 100644 index 000000000..957c62b61 --- /dev/null +++ b/mephisto/review_app/client/src/components/Tabs/Tabs.css @@ -0,0 +1,70 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.tabs { + margin-top: 20px; +} + +.tabs .tabs-nav { + --tab-nav-height: 58px; + --tab-nav-item-height: 41px; + + display: flex; + flex-direction: row; + gap: 4px; + padding-left: 20px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.tabs-empty { + height: 0; +} + +.tabs .tabs-nav .tabs-item { +} + +.tabs .tabs-nav .tabs-item.disabled { + background: none; + cursor: default; +} + +.tabs .tabs-nav .tabs-item:only-child { + display: none; +} + +.tabs .tabs-nav .tabs-item .tabs-item-link { +} + +.tabs .tabs-nav .tabs-item .tabs-item-link[aria-selected="true"] { + cursor: default; +} + +.tabs .tabs-nav .tabs-item .tabs-item-link[aria-selected="false"] { + color: #000000; + background-color: var(--bs-nav-tabs-border-color); +} + +.tabs .tabs-nav .tabs-item .tabs-item-link[aria-selected="false"]:hover { + background-color: #eeeeee; +} + +.tabs .tabs-content { + overflow-x: hidden; + overflow-y: auto; +} + +.tabs .tabs-content .tabs-content-pane { + margin-top: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.no-margins { + margin: 0; +} diff --git a/mephisto/review_app/client/src/components/Tabs/Tabs.tsx b/mephisto/review_app/client/src/components/Tabs/Tabs.tsx new file mode 100644 index 000000000..eeff2562e --- /dev/null +++ b/mephisto/review_app/client/src/components/Tabs/Tabs.tsx @@ -0,0 +1,101 @@ +import * as React from "react"; +import { useEffect, useRef, useState } from "react"; +import { Nav, Tab } from "react-bootstrap"; +import "./Tabs.css"; + +type TabsPropsType = { + activeTabName?: string; + navClassName?: string; + onPick?: (tabName: string) => void; + tabs: TabType[]; +}; + +let userKey = null; + +function Tabs(props: TabsPropsType) { + const { tabs, activeTabName } = props; + + const firstActiveTab = tabs.find((tab) => !tab.disabled); + + const [activeKey, setActiveKey] = useState( + activeTabName || firstActiveTab?.name + ); + + const tabContent = useRef(null); + + const onPick = (key: string) => { + userKey = key; + setActiveKey(key); + props.onPick && props.onPick(key); + }; + + useEffect(() => { + let tab = tabs.find((tab) => tab.name === userKey && !tab.disabled); + if (!tab) { + tab = tabs.find((tab) => !tab.disabled); + } + setActiveKey(activeTabName || tab.name); + }, [tabs]); + + useEffect(() => { + if (tabContent.current) { + tabContent.current.scrollTop = 0; + } + }); + + return ( +
+ {/* Tabs panel */} + onPick(k)}> + + + {/* Selected tab content */} + + {props.tabs.map((tab: TabType, i: number) => { + return ( + + {tab.children} + + ); + })} + + +
+ ); +} + +export default Tabs; diff --git a/mephisto/review_app/client/src/consts/format.ts b/mephisto/review_app/client/src/consts/format.ts new file mode 100644 index 000000000..cd55eca3a --- /dev/null +++ b/mephisto/review_app/client/src/consts/format.ts @@ -0,0 +1,9 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const DEFAULT_DATE_FORMAT = "MMM D, YYYY"; + +export const DEFAULT_DATETIME_FORMAT = "MMMM Do YYYY, h:mm:ss a"; diff --git a/mephisto/review_app/client/src/consts/review.ts b/mephisto/review_app/client/src/consts/review.ts index c2b1f030e..5d3eae2d7 100644 --- a/mephisto/review_app/client/src/consts/review.ts +++ b/mephisto/review_app/client/src/consts/review.ts @@ -53,3 +53,18 @@ export const VIDEO_TYPES_BY_EXT = { mpeg: "video/mpeg", webm: "video/webm", }; + +export const NEW_QUALIFICATION_NAME_LENGTH = 50; + +export const NEW_QUALIFICATION_DESCRIPTION_LENGTH = 500; + +export const EDIT_GRANTED_QUALIFICATION_VALUE_LENGTH = 50; + +export const EDIT_GRANTED_QUALIFICATION_EXPLANATION_LENGTH = 500; + +export const STATUS_COLOR_CLASS_MAPPING = { + accepted: "text-success", + approved: "text-success", + rejected: "text-danger", + soft_rejected: "text-warning", +}; diff --git a/mephisto/review_app/client/src/helpers.ts b/mephisto/review_app/client/src/helpers.ts index f4751691e..cc6395386 100644 --- a/mephisto/review_app/client/src/helpers.ts +++ b/mephisto/review_app/client/src/helpers.ts @@ -4,6 +4,7 @@ * LICENSE file in the root directory of this source tree. */ +import { SortArrowsState } from "components/ColumnTitleWithSort/ColumnTitleWithSort"; import cloneDeep from "lodash/cloneDeep"; export const updateModalState = ( @@ -31,3 +32,32 @@ export function setPageTitle(title: string) { export function capitalizeString(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } + +export function setResponseErrors( + setErrorsFunc: Function, + errorResponse: ErrorResponseType | null +) { + if (errorResponse) { + setErrorsFunc((oldErrors) => [...oldErrors, ...[errorResponse.error]]); + } +} + +export function onClickSortTableColumn( + columnName: string, + state: SortArrowsState, + onChangeSortParamFunc: Function, + setCurrentSortFunc: Function +) { + let _sortParam = ""; + if (state === SortArrowsState.INACTIVE) { + _sortParam = ""; + } else if (state === SortArrowsState.ASC) { + _sortParam = columnName; + } else if (state === SortArrowsState.DESC) { + _sortParam = `-${columnName}`; + } else { + } + + onChangeSortParamFunc(_sortParam); + setCurrentSortFunc({ column: columnName, state: state }); +} diff --git a/mephisto/review_app/client/src/pages/QualificationPage/DeleteQualificationModal/DeleteQualificationModal.css b/mephisto/review_app/client/src/pages/QualificationPage/DeleteQualificationModal/DeleteQualificationModal.css new file mode 100644 index 000000000..af9674cf2 --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/DeleteQualificationModal/DeleteQualificationModal.css @@ -0,0 +1,39 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.delete-qualification-modal { +} + +.delete-qualification-modal .modal-dialog .modal-header { + background-color: #ecdadf; + display: flex; + justify-content: center; + padding: 5px; + border-radius: 0; +} + +.delete-qualification-modal .modal-dialog .modal-header .modal-title { + font-size: 26px; +} + +.delete-qualification-modal .modal-dialog .modal-header .btn-close { + position: absolute; + right: 14px; +} + +.delete-qualification-modal .modal-dialog .modal-content { + border-radius: initial; +} + +.delete-qualification-modal + .modal-dialog + .modal-content + .modal-footer + .delete-qualification-buttons { + width: 100%; + display: flex; + justify-content: space-between; +} diff --git a/mephisto/review_app/client/src/pages/QualificationPage/DeleteQualificationModal/DeleteQualificationModal.tsx b/mephisto/review_app/client/src/pages/QualificationPage/DeleteQualificationModal/DeleteQualificationModal.tsx new file mode 100644 index 000000000..a353fe3b5 --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/DeleteQualificationModal/DeleteQualificationModal.tsx @@ -0,0 +1,72 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from "react"; +import { Button, Modal } from "react-bootstrap"; +import "./DeleteQualificationModal.css"; + +type DeleteQualificationModalPropsType = { + grantedQualificationsAmount: number; + onSubmit: Function; + setErrors: Function; + setShow: React.Dispatch>; + show: boolean; +}; + +function DeleteQualificationModal(props: DeleteQualificationModalPropsType) { + // Methods + + function onModalClose() { + props.setShow(!props.show); + } + + return ( + props.show && ( + + + Delete qualification + + + + {props.grantedQualificationsAmount === 0 ? ( + <>Are you sure you want to delete it? + ) : ( + <> + This qualification was granted {props.grantedQualificationsAmount}{" "} + times - are you sure you want to delete it? + + )} + + + +
+ + + +
+
+
+ ) + ); +} + +export default DeleteQualificationModal; diff --git a/mephisto/review_app/client/src/pages/QualificationPage/EditQualificationModal/EditQualificationModal.css b/mephisto/review_app/client/src/pages/QualificationPage/EditQualificationModal/EditQualificationModal.css new file mode 100644 index 000000000..45890b0ac --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/EditQualificationModal/EditQualificationModal.css @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.edit-qualification-modal { +} + +.edit-qualification-modal .modal-dialog .modal-header { + background-color: #ecdadf; + display: flex; + justify-content: center; + padding: 5px; + border-radius: 0; +} + +.edit-qualification-modal .modal-dialog .modal-header .modal-title { + font-size: 26px; +} + +.edit-qualification-modal .modal-dialog .modal-header .btn-close { + position: absolute; + right: 14px; +} + +.edit-qualification-modal .modal-dialog .modal-content { + border-radius: initial; +} + +.edit-qualification-modal .edit-qualification-form > * input, +.edit-qualification-modal .edit-qualification-form > * textarea { + border: 1px solid black; +} + +.edit-qualification-modal + .modal-dialog + .modal-content + .modal-footer + .edit-qualification-buttons { + width: 100%; + display: flex; + justify-content: space-between; +} diff --git a/mephisto/review_app/client/src/pages/QualificationPage/EditQualificationModal/EditQualificationModal.tsx b/mephisto/review_app/client/src/pages/QualificationPage/EditQualificationModal/EditQualificationModal.tsx new file mode 100644 index 000000000..b9ecc508a --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/EditQualificationModal/EditQualificationModal.tsx @@ -0,0 +1,152 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + NEW_QUALIFICATION_DESCRIPTION_LENGTH, + NEW_QUALIFICATION_NAME_LENGTH, +} from "consts/review"; +import cloneDeep from "lodash/cloneDeep"; +import * as React from "react"; +import { useEffect } from "react"; +import { Button, Col, Form, Modal, Row } from "react-bootstrap"; +import "./EditQualificationModal.css"; + +export type EditQualificationFormType = { + description: string; + name: string; +}; + +const DEFAULT_FORM_STATE: EditQualificationFormType = { + description: "", + name: "", +}; + +type EditQualificationModalPropsType = { + onSubmit: Function; + qualification: QualificationType; + setErrors: Function; + setShow: React.Dispatch>; + show: boolean; +}; + +function EditQualificationModal(props: EditQualificationModalPropsType) { + const [form, setForm] = React.useState( + cloneDeep(DEFAULT_FORM_STATE) + ); + const [formIsValid, setFormIsValid] = React.useState(false); + + // Methods + + function onModalClose() { + props.setShow(!props.show); + } + + function updateForm(fieldName: string, value: string) { + setForm({ ...form, [fieldName]: value }); + } + + // Effects + + useEffect(() => { + if (form.name !== "") { + setFormIsValid(true); + } else { + setFormIsValid(false); + } + }, [form]); + + useEffect(() => { + if (props.qualification) { + setForm({ + name: props.qualification.name, + description: props.qualification.description, + }); + } + }, [props.qualification]); + + return ( + props.show && ( + + + Edit qualification + + + +
{ + e.preventDefault(); + }} + > + + + Name + + + + updateForm("name", e.target.value)} + /> + + + + + + Description + + + + updateForm("description", e.target.value)} + /> + + +
+
+ + +
+ + + +
+
+
+ ) + ); +} + +export default EditQualificationModal; diff --git a/mephisto/review_app/client/src/pages/QualificationPage/GrantedQualificationsTable/GrantedQualificationsTable.css b/mephisto/review_app/client/src/pages/QualificationPage/GrantedQualificationsTable/GrantedQualificationsTable.css new file mode 100644 index 000000000..097b0156b --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/GrantedQualificationsTable/GrantedQualificationsTable.css @@ -0,0 +1,115 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.granted-qualification-table { + width: 100%; +} + +.granted-qualification-table .titles-row th { + background-color: #ecdadf; +} + +.granted-qualification-table .titles-row .qualification { + width: 350px; + max-width: 350px; +} + +.granted-qualification-table .titles-row .value-granted { + width: 130px; + text-align: center; + white-space: nowrap; +} + +.granted-qualification-table .titles-row .date-granted { + width: 130px; + text-align: center; +} + +.granted-qualification-table .titles-row .task { + width: 130px; + text-align: center; +} + +.granted-qualification-table .titles-row .worker { + width: 80px; + text-align: center; +} + +.granted-qualification-table .titles-row .unit { + width: 80px; + text-align: center; +} + +.granted-qualification-table .titles-row .actions { + width: 80px; + text-align: center; +} + +.granted-qualification-table .value-row:not(.no-hover) { + cursor: pointer; +} + +.granted-qualification-table .value-row:not(.no-hover):hover td { + background-color: rgba(236, 218, 223, 0.3); +} + +.granted-qualification-table .value-row .task { + width: 350px; + max-width: 350px; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.granted-qualification-table .value-row .qualification.text-primary { + cursor: pointer; +} +.granted-qualification-table .value-row .qualification.text-primary:hover { + text-decoration: underline; +} + +.granted-qualification-table .value-row .value-granted, +.granted-qualification-table .value-row .date-granted, +.granted-qualification-table .value-row .task, +.granted-qualification-table .value-row .worker { + text-align: center; +} + +.granted-qualification-table .value-row .units { + width: 280px; + max-width: 280px; + text-align: left; +} + +.granted-qualification-table .value-row .units .unit { + display: inline-flex; +} + +.granted-qualification-table .value-row .units .unit .unit-value { + margin-right: 4px; +} + +.granted-qualification-table .value-row .units .unit .text-primary { + cursor: pointer; + text-decoration: initial; +} + +.granted-qualification-table .value-row .units .unit .text-primary:hover { + text-decoration: underline; +} + +.granted-qualification-table .value-row .units .unit .task-name { + display: inline-block; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.granted-qualification-table .value-row .units .unit .unit-id { + display: inline-block; +} diff --git a/mephisto/review_app/client/src/pages/QualificationPage/GrantedQualificationsTable/GrantedQualificationsTable.tsx b/mephisto/review_app/client/src/pages/QualificationPage/GrantedQualificationsTable/GrantedQualificationsTable.tsx new file mode 100644 index 000000000..908291b34 --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/GrantedQualificationsTable/GrantedQualificationsTable.tsx @@ -0,0 +1,180 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import ColumnTitleWithSort, { + SortArrowsState, +} from "components/ColumnTitleWithSort/ColumnTitleWithSort"; +import { DEFAULT_DATE_FORMAT, DEFAULT_DATETIME_FORMAT } from "consts/format"; +import { onClickSortTableColumn } from "helpers"; +import * as moment from "moment/moment"; +import * as React from "react"; +import { Button, Table } from "react-bootstrap"; +import { Link } from "react-router-dom"; +import urls from "urls"; +import "./GrantedQualificationsTable.css"; + +type CurrentSortType = { + column: string; + state: SortArrowsState; +}; + +type GrantedQualificationTablePropsType = { + grantedQualifications: FullGrantedQualificationType[]; + onChangeSortParam: Function; + setEditModalGrantedQualification: Function; + setEditModalShow: Function; + setErrors: Function; +}; + +function GrantedQualificationsTable(props: GrantedQualificationTablePropsType) { + const [currentSort, setCurrentSort] = React.useState(null); + + return ( + + + + + + + + + + + + + + {props.grantedQualifications && + props.grantedQualifications.map( + (gq: FullGrantedQualificationType, index: number) => { + const grantedAt = moment(gq.granted_at).format( + DEFAULT_DATE_FORMAT + ); + const grantedAtFull = moment(gq.granted_at).format( + DEFAULT_DATETIME_FORMAT + ); + + return ( + + + + + + + + + ); + } + )} + +
+ Worker + + { + onClickSortTableColumn( + "value_current", + state, + props.onChangeSortParam, + setCurrentSort + ); + }} + state={ + currentSort?.column === "value_current" + ? currentSort?.state + : SortArrowsState.INACTIVE + } + title={Current value} + /> + + { + onClickSortTableColumn( + "granted_at", + state, + props.onChangeSortParam, + setCurrentSort + ); + }} + state={ + currentSort?.column === "granted_at" + ? currentSort?.state + : SortArrowsState.INACTIVE + } + title={Updated} + /> + + Granted values +
{gq.worker_name}{gq.value_current} + {grantedAt} + + {gq.units.map((unit: FGQUnit, index: number) => { + let valueAddition = ""; + if (unit.unit_id) { + const unitPageUrl = urls.client.taskUnit( + unit.task_id, + unit.unit_id + ); + valueAddition = unitPageUrl; + } else { + const creationDate = moment(unit.creation_date).format( + DEFAULT_DATE_FORMAT + ); + valueAddition = creationDate; + } + + const creationDateFull = moment( + unit.creation_date + ).format(DEFAULT_DATETIME_FORMAT); + + return ( + +
+ {unit.value}( + {unit.unit_id ? ( + + {unit.task_name} + + ) : ( + + {valueAddition} + + )} + ) +
+ +
+
+ ); + })} +
+ +
+ ); +} + +export default GrantedQualificationsTable; diff --git a/mephisto/review_app/client/src/pages/QualificationPage/QualificationPage.css b/mephisto/review_app/client/src/pages/QualificationPage/QualificationPage.css new file mode 100644 index 000000000..bae2fae3c --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/QualificationPage.css @@ -0,0 +1,42 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.qualification { +} + +.qualification .header { + margin-bottom: 40px; + padding: 10px 20px; + background-color: #ecdadf; + display: flex; + flex-direction: row; + gap: 10px; + justify-content: space-between; +} + +.qualification .header .qualification-info { + display: flex; + flex-direction: column; + gap: 20px; +} + +.qualification .header .qualification-info .qualification-name { + font-size: 25px; +} + +.qualification .header .qualification-info .qualification-description { + max-width: 800px; +} + +.qualification .header .qualification-info .qualification-date { +} + +.qualification .header .header-buttons { + display: flex; + flex-direction: row; + gap: 10px; + align-items: flex-start; +} diff --git a/mephisto/review_app/client/src/pages/QualificationPage/QualificationPage.tsx b/mephisto/review_app/client/src/pages/QualificationPage/QualificationPage.tsx new file mode 100644 index 000000000..d51b3d021 --- /dev/null +++ b/mephisto/review_app/client/src/pages/QualificationPage/QualificationPage.tsx @@ -0,0 +1,305 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import EditGrantedQualificationModal, { + EditGrantedQualificationFormType, +} from "components/EditGrantedQualificationModal/EditGrantedQualificationModal"; +import Preloader from "components/Preloader/Preloader"; +import TasksHeader from "components/TasksHeader/TasksHeader"; +import { DEFAULT_DATE_FORMAT } from "consts/format"; +import { setResponseErrors } from "helpers"; +import cloneDeep from "lodash/cloneDeep"; +import * as moment from "moment"; +import * as React from "react"; +import { useEffect } from "react"; +import { Button } from "react-bootstrap"; +import { useParams } from "react-router-dom"; +import { + deleteQualification, + getGrantedQualifications, + getQualification, + getQualificationDetails, + patchQualification, + patchQualificationGrantWorker, + patchQualificationRevokeWorker, +} from "requests/qualifications"; +import urls from "urls"; +import DeleteQualificationModal from "./DeleteQualificationModal/DeleteQualificationModal"; +import EditQualificationModal from "./EditQualificationModal/EditQualificationModal"; +import GrantedQualificationsTable from "./GrantedQualificationsTable/GrantedQualificationsTable"; +import "./QualificationPage.css"; + +type GrantedQualificationsParamsType = { + qualification_id?: string; + sort?: string; +}; + +type ParamsType = { + id: string; +}; + +type QualificationPagePropsType = { + setErrors: Function; +}; + +function QualificationPage(props: QualificationPagePropsType) { + const params = useParams(); + + const qualificationId = params.id; + + const [qualification, setQualification] = React.useState( + null + ); + const [grantedQualifications, setGrantedQualifications] = React.useState< + FullGrantedQualificationType[] + >(null); + const [ + grantedQualificationsParams, + setGrantedQualificationsParams, + ] = React.useState({ + qualification_id: qualificationId, + }); + const [loading, setLoading] = React.useState(false); + const [ + editQualificationModalShow, + setEditQualificationModalShow, + ] = React.useState(false); + const [ + deleteQualificationModalShow, + setDeleteQualificationModalShow, + ] = React.useState(false); + const [ + grantedQualificationsAmount, + setGrantedQualificationsAmount, + ] = React.useState(0); + const [ + editGrantedQualificationModalShow, + setEditGrantedQualificationModalShow, + ] = React.useState(false); + const [ + editModalGrantedQualification, + setEditModalGrantedQualification, + ] = React.useState(null); + + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); + + const hasGrantedQualifications = + grantedQualifications && grantedQualifications.length !== 0; + + // Methods + + function requestQualification() { + getQualification(qualificationId, setQualification, setLoading, onError); + } + + function requestGrantedQualifications() { + getGrantedQualifications( + setGrantedQualifications, + setLoading, + onError, + grantedQualificationsParams + ); + } + + function onClickDeleteButton() { + function onSuccess(amount: number) { + setGrantedQualificationsAmount(amount); + setDeleteQualificationModalShow(true); + } + + getQualificationDetails( + qualificationId, + (data: QualificationDetailsType) => + onSuccess(data.granted_qualifications_count), + () => null, + onError + ); + } + + function onEditQualificationModalSubmit(data: CreateQualificationFormType) { + function onSuccess() { + requestQualification(); + setEditQualificationModalShow(false); + } + + patchQualification(qualificationId, onSuccess, setLoading, onError, data); + } + + function onDeleteModalSubmit() { + function onSuccess() { + setDeleteQualificationModalShow(false); + // Redirect to Tasks page + window.location.replace(urls.client.tasks); + } + + deleteQualification(qualificationId, onSuccess, setLoading, onError); + } + + function onEditGrantedQualificationModalSubmit( + qualificationId: string, + workerId: string, + data: EditGrantedQualificationFormType + ) { + function onSuccess() { + requestGrantedQualifications(); + setEditGrantedQualificationModalShow(false); + } + + patchQualificationGrantWorker( + qualificationId, + workerId, + onSuccess, + setLoading, + onError, + data + ); + } + + function onEditGrantedQualificationModalRevoke( + qualificationId: string, + workerId: string + ) { + function onSuccess() { + requestGrantedQualifications(); + setEditGrantedQualificationModalShow(false); + } + + patchQualificationRevokeWorker( + qualificationId, + workerId, + onSuccess, + setLoading, + onError, + null + ); + } + + function onChangeTableSortParam(param: string) { + setGrantedQualificationsParams( + (oldValue: GrantedQualificationsParamsType) => { + const newValue = cloneDeep(oldValue); + if (param) { + newValue.sort = param; + } else { + delete newValue.sort; + } + return newValue; + } + ); + } + + // Effects + useEffect(() => { + if (qualification === null) { + requestQualification(); + } + }, []); + + useEffect(() => { + if (qualification === null) { + return; + } + + if (grantedQualifications === null) { + requestGrantedQualifications(); + } + + document.title = `Mephisto - Task Review - Qualification "${qualification.name}"`; + }, [qualification]); + + useEffect(() => { + requestGrantedQualifications(); + }, [grantedQualificationsParams]); + + return ( +
+ + + {!loading && qualification && ( +
+
+
+ Qualification "{qualification.name}" +
+ + {qualification.description && ( +
+ {qualification.description} +
+ )} + +
+ Date created:{" "} + {moment(qualification.creation_date).format(DEFAULT_DATE_FORMAT)} +
+
+ +
+ + + +
+
+ )} + + {hasGrantedQualifications ? ( + onChangeTableSortParam(param)} + setEditModalGrantedQualification={setEditModalGrantedQualification} + setEditModalShow={setEditGrantedQualificationModalShow} + setErrors={props.setErrors} + /> + ) : ( +
+ This qualification has not been granted to any worker yet. +
+ )} + + + + + + + + +
+ ); +} + +export default QualificationPage; diff --git a/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.css b/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.css deleted file mode 100644 index 4c61eeda4..000000000 --- a/mephisto/review_app/client/src/pages/TaskPage/ModalForm/ModalForm.css +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) Meta Platforms and its affiliates. - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -.review-form > * input, -.review-form > * select, -.review-form > * textarea { - border: 1px solid black; -} - -.review-form .second-line, -.review-form .third-line { - margin-top: 10px; - box-sizing: border-box; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - padding-left: 25px; -} - -.review-form .third-line { - margin-top: 10px; -} - -.new-qualification-name-button { - width: 100%; -} diff --git a/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.css b/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.css index b54a3dd40..6523a50e3 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.css +++ b/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.css @@ -16,6 +16,11 @@ font-size: 26px; } +.review-modal .modal-dialog .modal-header .btn-close { + position: absolute; + right: 14px; +} + /* Body */ .review-modal .modal-dialog .modal-content { border-radius: initial; @@ -28,17 +33,6 @@ justify-content: space-between; } -.review-modal - .modal-dialog - .modal-content - .modal-footer - .review-buttons - .btn-cancel-button { - text-decoration: none; - color: grey; - border: none; -} - .review-modal .modal-dialog .modal-content diff --git a/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.tsx b/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.tsx index 95545345b..be0a61946 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/ReviewModal/ReviewModal.tsx @@ -7,7 +7,7 @@ import { ReviewType } from "consts/review"; import * as React from "react"; import { Button, Form, Modal } from "react-bootstrap"; -import ModalForm from "../ModalForm/ModalForm"; +import ReviewModalForm from "../ReviewModalForm/ReviewModalForm"; import "./ReviewModal.css"; const ReviewTypeButtonClassMapping = { @@ -38,12 +38,12 @@ function ReviewModal(props: ReviewModalProps) { return ( props.show && ( - + {props.data.title} -
+ + + + + + Please press "Add" button to assign indicated qualification + + {Object.keys(selectedQualifications).length > 0 && ( +
+ {Object.values(selectedQualifications).map( + ( + selectedQualification: SelectedQualificationType, + index: number + ) => { + return ( + + + {selectedQualification.qualification_name} + + + +
+ {selectedQualification.value} +
+ + + +
+ ); + } + )} +
+ )} + {props.data.form.showNewQualification && ( - - - - onChangeNewQualificationValue(e.target.value) - } - /> - - - - - + <> + + +
+ +
+ + + + + onChangeNewQualificationValue( + "newQualificationName", + e.target.value + ) + } + /> + + + onChangeNewQualificationValue( + "newQualificationDescription", + e.target.value + ) + } + /> + + + + + + + + + + Create new qualification that can be assigned to this or + other workers + + + )} )} @@ -334,7 +507,7 @@ function ModalForm(props: ModalFormProps) { onChangeUnassignQualification(e.target.value) } @@ -454,4 +627,4 @@ function ModalForm(props: ModalFormProps) { ); } -export default ModalForm; +export default ReviewModalForm; diff --git a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css index 45b6f128b..f48c826bc 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css +++ b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.css @@ -73,14 +73,6 @@ cursor: default; } -.task .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; -} - .unit-preview-iframe { width: 100%; } diff --git a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx index cec96e49f..05e2cfe5f 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/TaskPage.tsx @@ -6,6 +6,7 @@ import InitialParametersCollapsable from "components/InitialParametersCollapsable/InitialParametersCollapsable"; import { InReviewFileModal } from "components/InReviewFileModal/InReviewFileModal"; +import Preloader from "components/Preloader/Preloader"; import ResultsCollapsable from "components/ResultsCollapsable/ResultsCollapsable"; import VideoAnnotatorWebVTTCollapsable from "components/VideoAnnotatorWebVTTCollapsable/VideoAnnotatorWebVTTCollapsable"; import WorkerOpinionCollapsable from "components/WorkerOpinionCollapsable/WorkerOpinionCollapsable"; @@ -14,11 +15,11 @@ import { MESSAGES_IN_REVIEW_FILE_DATA_KEY, ReviewType, } from "consts/review"; -import { setPageTitle, updateModalState } from "helpers"; +import { setPageTitle, setResponseErrors, updateModalState } from "helpers"; import cloneDeep from "lodash/cloneDeep"; import * as React from "react"; import { useEffect } from "react"; -import { Button, Spinner } from "react-bootstrap"; +import { Button } from "react-bootstrap"; import { useNavigate, useParams } from "react-router-dom"; import { postQualificationGrantWorker, @@ -33,7 +34,7 @@ import { postUnitsReject, postUnitsSoftReject, } from "requests/units"; -import { postWorkerBlock } from "requests/workers"; +import { postWorkerBlock, postWorkerGrant } from "requests/workers"; import urls from "urls"; import { APPROVE_MODAL_DATA_STATE, @@ -128,6 +129,9 @@ function TaskPage(props: TaskPagePropsType) { InReviewFileModalDataType >({}); + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); + function getUnitDataFolder(): string { const unitDataFolderStartIndex = currentUnitDetails.unit_data_folder.indexOf( "data/data" @@ -275,17 +279,16 @@ function TaskPage(props: TaskPagePropsType) { // Grant Qualification if ( _modalData.form.checkboxAssignQualification && - _modalData.form.qualification + _modalData.form.selectedQualifications?.length > 0 ) { - postQualificationGrantWorker( - _modalData.form.qualification, + postWorkerGrant( currentWorkerOnReview, () => null, setLoading, onError, { unit_ids: unitIds, - value: _modalData.form.qualificationValue, + qualification_grants: _modalData.form.selectedQualifications, } ); } @@ -293,17 +296,16 @@ function TaskPage(props: TaskPagePropsType) { // Revoke Qualification if ( _modalData.form.checkboxAssignQualification && - _modalData.form.qualification + _modalData.form.selectQualification ) { - postQualificationRevokeWorker( - _modalData.form.qualification, + postWorkerGrant( currentWorkerOnReview, () => null, setLoading, onError, { unit_ids: unitIds, - value: _modalData.form.qualificationValue, + qualification_grants: _modalData.form.selectedQualifications, } ); } @@ -311,10 +313,10 @@ function TaskPage(props: TaskPagePropsType) { // Revoke Qualification if ( _modalData.form.checkboxUnassignQualification && - _modalData.form.qualification + _modalData.form.selectQualification ) { postQualificationRevokeWorker( - _modalData.form.qualification, + _modalData.form.selectQualification, currentWorkerOnReview, () => null, setLoading, @@ -421,12 +423,6 @@ function TaskPage(props: TaskPagePropsType) { } } - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } - // [RECEIVING WIDGET DATA] // --- function sendDataToUnitIframe(data: object) { @@ -647,13 +643,7 @@ function TaskPage(props: TaskPagePropsType) {
{/* Preloader when we request tasks */} - {loading && ( -
- - Loading... - -
- )} + {/* Initial Unit parameters */} {currentUnitDetails?.inputs && ( diff --git a/mephisto/review_app/client/src/pages/TaskPage/modalData.tsx b/mephisto/review_app/client/src/pages/TaskPage/modalData.tsx index 3aed9ab52..2baac0796 100644 --- a/mephisto/review_app/client/src/pages/TaskPage/modalData.tsx +++ b/mephisto/review_app/client/src/pages/TaskPage/modalData.tsx @@ -17,10 +17,12 @@ export const APPROVE_MODAL_DATA_STATE: ModalDataType = { checkboxGiveBonus: false, checkboxReviewNote: false, checkboxReviewNoteSend: false, - newQualificationValue: "", - qualification: null, - qualificationValue: 1, + newQualificationDescription: "", + newQualificationName: "", reviewNote: "", + selectQualification: null, + selectQualificationValue: 1, + selectedQualifications: null, showNewQualification: false, }, title: "Approve Unit", @@ -37,10 +39,12 @@ export const SOFT_REJECT_MODAL_DATA_STATE: ModalDataType = { checkboxAssignQualification: false, checkboxReviewNote: false, checkboxReviewNoteSend: false, - newQualificationValue: "", - qualification: null, - qualificationValue: 1, + newQualificationDescription: "", + newQualificationName: "", reviewNote: "", + selectQualification: null, + selectQualificationValue: 1, + selectedQualifications: null, showNewQualification: false, }, title: "Soft-Reject Unit", @@ -55,14 +59,15 @@ export const REJECT_MODAL_DATA_STATE: ModalDataType = { title: "Reject Unit", type: ReviewType.REJECT, form: { - checkboxUnassignQualification: false, + bonus: null, + checkboxBanWorker: false, checkboxReviewNote: false, checkboxReviewNoteSend: false, - checkboxBanWorker: false, + checkboxUnassignQualification: false, reviewNote: "", - qualification: null, - qualificationValue: 1, - bonus: null, + selectQualification: null, + selectQualificationValue: 1, + selectedQualifications: null, }, }; diff --git a/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.css b/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.css index 8f3696abd..7c06a4598 100644 --- a/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.css +++ b/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.css @@ -61,11 +61,3 @@ color: #888888; font-style: italic; } - -.task-stats .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; -} diff --git a/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.tsx b/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.tsx index cf892615c..0687b38d1 100644 --- a/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskStatsPage/TaskStatsPage.tsx @@ -4,11 +4,11 @@ * LICENSE file in the root directory of this source tree. */ +import Preloader from "components/Preloader/Preloader"; import TasksHeader from "components/TasksHeader/TasksHeader"; -import { setPageTitle } from "helpers"; +import { setPageTitle, setResponseErrors } from "helpers"; import * as React from "react"; import { useEffect } from "react"; -import { Spinner } from "react-bootstrap"; import { useParams } from "react-router-dom"; import { getTaskStats } from "requests/tasks"; import { Histogram } from "./Histogram"; @@ -30,13 +30,10 @@ function TaskStatsPage(props: TaskStatsPagePropsType) { const [taskStats, setTaskStats] = React.useState(null); const [loading, setLoading] = React.useState(false); - const hasStats = taskStats && Object.keys(taskStats.stats).length != 0; + const hasStats = taskStats && Object.keys(taskStats.stats).length !== 0; - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); // Effects useEffect(() => { @@ -82,11 +79,7 @@ function TaskStatsPage(props: TaskStatsPagePropsType) {
{/* Preloader when we request task stats */} {loading ? ( -
- - Loading... - -
+ ) : ( <>
diff --git a/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.css b/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.css index 7817ec3b2..2daf62fbb 100644 --- a/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.css +++ b/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.css @@ -24,11 +24,3 @@ .task-timeline .content { padding: 10px 20px; } - -.task-timeline .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; -} diff --git a/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.tsx b/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.tsx index 3f13cb35b..e3ed4bdb2 100644 --- a/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.tsx +++ b/mephisto/review_app/client/src/pages/TaskTimelinePage/TaskTimelinePage.tsx @@ -4,11 +4,11 @@ * LICENSE file in the root directory of this source tree. */ +import Preloader from "components/Preloader/Preloader"; import TasksHeader from "components/TasksHeader/TasksHeader"; -import { setPageTitle } from "helpers"; +import { setPageTitle, setResponseErrors } from "helpers"; import * as React from "react"; import { useEffect } from "react"; -import { Spinner } from "react-bootstrap"; import { useParams } from "react-router-dom"; import { getTaskTimeline } from "requests/tasks"; import "./TaskTimelinePage.css"; @@ -29,11 +29,8 @@ function TaskTimelinePage(props: TaskTimelinePagePropsType) { ); const [loading, setLoading] = React.useState(false); - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); // Effects useEffect(() => { @@ -76,11 +73,7 @@ function TaskTimelinePage(props: TaskTimelinePagePropsType) {
{/* Preloader when we request task timeline */} {loading ? ( -
- - Loading... - -
+ ) : ( <> {!taskTimeline.server_is_available && ( diff --git a/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.css b/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.css index f64423210..dc9cee7ad 100644 --- a/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.css +++ b/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.css @@ -77,11 +77,3 @@ .units .units-table .unit-row .unit a:hover { text-decoration: underline; } - -.units .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; -} diff --git a/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.tsx b/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.tsx index ae8f400cb..a88f68799 100644 --- a/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskUnitsPage/TaskUnitsPage.tsx @@ -4,24 +4,21 @@ * LICENSE file in the root directory of this source tree. */ +import Preloader from "components/Preloader/Preloader"; import TasksHeader from "components/TasksHeader/TasksHeader"; -import { capitalizeString, setPageTitle } from "helpers"; +import { DEFAULT_DATE_FORMAT } from "consts/format"; +import { STATUS_COLOR_CLASS_MAPPING } from "consts/review"; +import { capitalizeString, setPageTitle, setResponseErrors } from "helpers"; import * as moment from "moment/moment"; import * as React from "react"; import { useEffect } from "react"; -import { Spinner, Table } from "react-bootstrap"; +import { Table } from "react-bootstrap"; import { Link, useParams } from "react-router-dom"; import { getTask } from "requests/tasks"; import { getUnits } from "requests/units"; import urls from "urls"; import "./TaskUnitsPage.css"; -const STATUS_COLOR_CLASS_MAPPING = { - accepted: "text-success", - rejected: "text-danger", - soft_rejected: "text-warning", -}; - type ParamsType = { id: string; }; @@ -37,11 +34,8 @@ function TaskUnitsPage(props: TaskUnitsPagePropsType) { const [units, setUnits] = React.useState>(null); const [loading, setLoading] = React.useState(false); - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); useEffect(() => { // Set default title @@ -101,8 +95,7 @@ function TaskUnitsPage(props: TaskUnitsPagePropsType) { {units && units.map((unit: UnitType, index: number) => { - const date = moment(unit.created_at).format("MMM D, YYYY"); - const nonClickable = !unit.is_reviewed; + const date = moment(unit.created_at).format(DEFAULT_DATE_FORMAT); const statusColorClass = STATUS_COLOR_CLASS_MAPPING[unit.status]; return ( @@ -137,13 +130,7 @@ function TaskUnitsPage(props: TaskUnitsPagePropsType) { {/* Preloader when we request units */} - {loading && ( -
- - Loading... - -
- )} +
); } diff --git a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css index 6f887aa4d..72cd5cb56 100644 --- a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css +++ b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.css @@ -77,11 +77,3 @@ .questions { margin-top: 5px; } - -.task-worker-opinions .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; -} diff --git a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx index 727b80506..0c3ce1708 100644 --- a/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx +++ b/mephisto/review_app/client/src/pages/TaskWorkerOpinionsPage/TaskWorkerOpinionsPage.tsx @@ -5,12 +5,12 @@ */ import { InReviewFileModal } from "components/InReviewFileModal/InReviewFileModal"; +import Preloader from "components/Preloader/Preloader"; import TasksHeader from "components/TasksHeader/TasksHeader"; import WorkerOpinionCollapsable from "components/WorkerOpinionCollapsable/WorkerOpinionCollapsable"; -import { setPageTitle } from "helpers"; +import { setPageTitle, setResponseErrors } from "helpers"; import * as React from "react"; import { useEffect } from "react"; -import { Spinner } from "react-bootstrap"; import { useParams } from "react-router-dom"; import { getTaskWorkerOpinions } from "requests/tasks"; import "./TaskWorkerOpinionsPage.css"; @@ -40,11 +40,8 @@ function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) { InReviewFileModalDataType >({}); - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); function onClickOnWorkerOpinionAttachment( file: WorkerOpinionAttachmentType, @@ -124,11 +121,7 @@ function TaskWorkerOpinionsPage(props: TaskWorkerOpinionsPagePropsType) {
{/* Preloader when we request task worker opinions */} {loading ? ( -
- - Loading... - -
+ ) : ( // Worker Opinions taskWorkerOpinions?.worker_opinions?.map( diff --git a/mephisto/review_app/client/src/pages/TasksPage/CreateQualificationModal/CreateQualificationModal.css b/mephisto/review_app/client/src/pages/TasksPage/CreateQualificationModal/CreateQualificationModal.css new file mode 100644 index 000000000..084768d81 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/CreateQualificationModal/CreateQualificationModal.css @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.create-qualification-modal { +} + +.create-qualification-modal .modal-dialog .modal-header { + background-color: #ecdadf; + display: flex; + justify-content: center; + padding: 5px; + border-radius: 0; +} + +.create-qualification-modal .modal-dialog .modal-header .modal-title { + font-size: 26px; +} + +.create-qualification-modal .modal-dialog .modal-header .btn-close { + position: absolute; + right: 14px; +} + +.create-qualification-modal .modal-dialog .modal-content { + border-radius: initial; +} + +.create-qualification-modal .create-qualification-form > * input, +.create-qualification-modal .create-qualification-form > * textarea { + border: 1px solid black; +} + +.create-qualification-modal + .modal-dialog + .modal-content + .modal-footer + .create-qualification-buttons { + width: 100%; + display: flex; + justify-content: space-between; +} diff --git a/mephisto/review_app/client/src/pages/TasksPage/CreateQualificationModal/CreateQualificationModal.tsx b/mephisto/review_app/client/src/pages/TasksPage/CreateQualificationModal/CreateQualificationModal.tsx new file mode 100644 index 000000000..6002cb87c --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/CreateQualificationModal/CreateQualificationModal.tsx @@ -0,0 +1,143 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + NEW_QUALIFICATION_DESCRIPTION_LENGTH, + NEW_QUALIFICATION_NAME_LENGTH, +} from "consts/review"; +import cloneDeep from "lodash/cloneDeep"; +import * as React from "react"; +import { useEffect } from "react"; +import { Button, Col, Form, Modal, Row } from "react-bootstrap"; +import "./CreateQualificationModal.css"; + +const DEFAULT_FORM_STATE: CreateQualificationFormType = { + description: "", + name: "", +}; + +type CreateQualificationModalPropsType = { + show: boolean; + setShow: React.Dispatch>; + onSubmit: Function; + setErrors: Function; +}; + +function CreateQualificationModal(props: CreateQualificationModalPropsType) { + const [form, setForm] = React.useState( + cloneDeep(DEFAULT_FORM_STATE) + ); + const [formIsValid, setFormIsValid] = React.useState(false); + + // Methods + + function onModalClose() { + props.setShow(!props.show); + } + + function updateForm(fieldName: string, value: string) { + setForm({ ...form, [fieldName]: value }); + } + + // Effects + + useEffect(() => { + if (form.name !== "") { + setFormIsValid(true); + } else { + setFormIsValid(false); + } + }, [form]); + + useEffect(() => { + if (props.show) { + setForm(cloneDeep(DEFAULT_FORM_STATE)); + } + }, [props.show]); + + return ( + props.show && ( + + + Create qualification + + + +
{ + e.preventDefault(); + }} + > + + + Name + + + + updateForm("name", e.target.value)} + /> + + + + + + Description + + + + updateForm("description", e.target.value)} + /> + + +
+
+ + +
+ + + +
+
+
+ ) + ); +} + +export default CreateQualificationModal; diff --git a/mephisto/review_app/client/src/pages/TasksPage/QualificationsTab/QualificationsTab.css b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTab/QualificationsTab.css new file mode 100644 index 000000000..cb7044805 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTab/QualificationsTab.css @@ -0,0 +1,44 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.qualifications-tab { + width: 100%; +} + +.qualifications-tab .qualification-actions { + height: 60px; + padding-left: 10px; + padding-right: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.qualifications-tab .qualification-actions .filter-qualifications { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +.qualifications-tab + .qualification-actions + .filter-qualifications + .select-qualifications-label { + white-space: nowrap; +} +.qualifications-tab + .qualification-actions + .filter-qualifications + .select-qualifications { + min-width: 300px; + max-width: 300px; +} + +.qualifications-tab .empty-message { + margin: 10px; +} diff --git a/mephisto/review_app/client/src/pages/TasksPage/QualificationsTab/QualificationsTab.tsx b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTab/QualificationsTab.tsx new file mode 100644 index 000000000..31c4a67c0 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTab/QualificationsTab.tsx @@ -0,0 +1,246 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import EditGrantedQualificationModal, { + EditGrantedQualificationFormType, +} from "components/EditGrantedQualificationModal/EditGrantedQualificationModal"; +import Preloader from "components/Preloader/Preloader"; +import { setResponseErrors } from "helpers"; +import cloneDeep from "lodash/cloneDeep"; +import * as React from "react"; +import { useEffect } from "react"; +import { Button } from "react-bootstrap"; +import { + getGrantedQualifications, + getQualifications, + patchQualificationGrantWorker, + patchQualificationRevokeWorker, + postQualification, +} from "requests/qualifications"; +import CreateQualificationModal from "../CreateQualificationModal/CreateQualificationModal"; +import QualificationsTable from "../QualificationsTable/QualificationsTable"; +import "./QualificationsTab.css"; + +const DEFAUTL_GRANTED_QUALIFICATIONS_PARAMS = {}; + +type GrantedQualificationsParamsType = { + qualification_id?: string; + sort?: string; +}; + +type QualificationsTabPropsType = { + setErrors: Function; +}; + +function QualificationsTab(props: QualificationsTabPropsType) { + const [grantedQualifications, setGrantedQualifications] = React.useState< + FullGrantedQualificationType[] + >(null); + const [ + grantedQualificationsParams, + setGrantedQualificationsParams, + ] = React.useState( + DEFAUTL_GRANTED_QUALIFICATIONS_PARAMS + ); + const [qualifications, setQualifications] = React.useState< + QualificationType[] + >([]); + const [loading, setLoading] = React.useState(false); + const [createModalShow, setCreateModalShow] = React.useState(false); + const [editModalShow, setEditModalShow] = React.useState(false); + const [ + editModalGrantedQualification, + setEditModalGrantedQualification, + ] = React.useState(null); + + const hasGrantedQualifications = + grantedQualifications && grantedQualifications.length !== 0; + + // Methods + + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); + + function requestQualifications() { + getQualifications(setQualifications, setLoading, onError); + } + + function requestGrantedQualifications() { + getGrantedQualifications( + setGrantedQualifications, + setLoading, + onError, + grantedQualificationsParams + ); + } + + function onCreateModalSubmit(data: CreateQualificationFormType) { + function onSuccess() { + requestQualifications(); + setCreateModalShow(false); + } + + postQualification(onSuccess, () => null, onError, data); + } + + function onEditModalSubmit( + qualificationId: string, + workerId: string, + data: EditGrantedQualificationFormType + ) { + function onSuccess() { + requestGrantedQualifications(); + setEditModalShow(false); + } + + patchQualificationGrantWorker( + qualificationId, + workerId, + onSuccess, + setLoading, + onError, + data + ); + } + + function onEditModalRevoke(qualificationId: string, workerId: string) { + function onSuccess() { + requestGrantedQualifications(); + setEditModalShow(false); + } + + patchQualificationRevokeWorker( + qualificationId, + workerId, + onSuccess, + setLoading, + onError, + null + ); + } + + function onSelectQualification(qualificationId: string) { + setGrantedQualificationsParams( + (oldValue: GrantedQualificationsParamsType) => { + const newValue = cloneDeep(oldValue); + if (qualificationId) { + newValue.qualification_id = qualificationId; + } else { + delete newValue.qualification_id; + } + return newValue; + } + ); + } + + function onChangeTableSortParam(param: string) { + setGrantedQualificationsParams( + (oldValue: GrantedQualificationsParamsType) => { + const newValue = cloneDeep(oldValue); + if (param) { + newValue.sort = param; + } else { + delete newValue.sort; + } + return newValue; + } + ); + } + + // Effects + + useEffect(() => { + document.title = "Mephisto - Task Review - All Qualifications"; + + if (qualifications.length === 0) { + requestQualifications(); + } + + if (grantedQualifications === null) { + requestGrantedQualifications(); + } + }, []); + + useEffect(() => { + requestGrantedQualifications(); + }, [grantedQualificationsParams]); + + return ( +
+
+
+ + + +
+ +
+ +
+
+ + {hasGrantedQualifications ? ( + onChangeTableSortParam(param)} + setEditModalGrantedQualification={setEditModalGrantedQualification} + setEditModalShow={setEditModalShow} + setErrors={props.setErrors} + /> + ) : ( +
+ No qualifications has been granted to any worker yet. +
+ )} + + + + + + +
+ ); +} + +export default QualificationsTab; diff --git a/mephisto/review_app/client/src/pages/TasksPage/QualificationsTable/QualificationsTable.css b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTable/QualificationsTable.css new file mode 100644 index 000000000..b23252f1e --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTable/QualificationsTable.css @@ -0,0 +1,116 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.qualifications-table { + width: 100%; +} + +.qualifications-table .titles-row th { + background-color: #ecdadf; +} + +.qualifications-table .titles-row .qualification { + width: 350px; + max-width: 350px; +} + +.qualifications-table .titles-row .value-granted { + width: 130px; + text-align: center; + white-space: nowrap; +} + +.qualifications-table .titles-row .date-granted { + width: 130px; + text-align: center; +} + +.qualifications-table .titles-row .task { + width: 130px; + text-align: center; +} + +.qualifications-table .titles-row .worker { + width: 80px; + text-align: center; +} + +.qualifications-table .titles-row .unit { + width: 80px; + text-align: center; +} + +.qualifications-table .titles-row .actions { + width: 80px; + text-align: center; +} + +.qualifications-table .value-row:not(.no-hover) { + cursor: pointer; +} + +.qualifications-table .value-row:not(.no-hover):hover td { + background-color: rgba(236, 218, 223, 0.3); +} + +.qualifications-table .value-row .qualification { + width: 350px; + max-width: 350px; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qualifications-table .value-row .qualification .qualification-link { + text-decoration: initial; + cursor: pointer; +} +.qualifications-table .value-row .qualification .qualification-link:hover { + text-decoration: underline; +} + +.qualifications-table .value-row .value-granted, +.qualifications-table .value-row .date-granted, +.qualifications-table .value-row .task, +.qualifications-table .value-row .worker { + text-align: center; +} + +.qualifications-table .value-row .units { + width: 280px; + max-width: 280px; + text-align: left; +} + +.qualifications-table .value-row .units .unit { + display: inline-flex; +} + +.qualifications-table .value-row .units .unit .unit-value { + margin-right: 4px; +} + +.qualifications-table .value-row .units .unit .text-primary { + cursor: pointer; + text-decoration: initial; +} + +.qualifications-table .value-row .units .unit .text-primary:hover { + text-decoration: underline; +} + +.qualifications-table .value-row .units .unit .task-name { + display: inline-block; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.qualifications-table .value-row .units .unit .unit-id { + display: inline-block; +} diff --git a/mephisto/review_app/client/src/pages/TasksPage/QualificationsTable/QualificationsTable.tsx b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTable/QualificationsTable.tsx new file mode 100644 index 000000000..c7c3ec403 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/QualificationsTable/QualificationsTable.tsx @@ -0,0 +1,193 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import ColumnTitleWithSort, { + SortArrowsState, +} from "components/ColumnTitleWithSort/ColumnTitleWithSort"; +import { DEFAULT_DATE_FORMAT, DEFAULT_DATETIME_FORMAT } from "consts/format"; +import { onClickSortTableColumn } from "helpers"; +import * as moment from "moment/moment"; +import * as React from "react"; +import { Button, Table } from "react-bootstrap"; +import { Link } from "react-router-dom"; +import urls from "urls"; +import "./QualificationsTable.css"; + +type CurrentSortType = { + column: string; + state: SortArrowsState; +}; + +type QualificationsTablePropsType = { + grantedQualifications: FullGrantedQualificationType[]; + onChangeSortParam: Function; + setEditModalGrantedQualification: Function; + setEditModalShow: Function; + setErrors: Function; +}; + +function QualificationsTable(props: QualificationsTablePropsType) { + const [currentSort, setCurrentSort] = React.useState(null); + + return ( + + + + + + + + + + + + + + + {props.grantedQualifications && + props.grantedQualifications.map( + (gq: FullGrantedQualificationType, index: number) => { + const grantedAtShort = moment(gq.granted_at).format( + DEFAULT_DATE_FORMAT + ); + const grantedAtFull = moment(gq.granted_at).format( + DEFAULT_DATETIME_FORMAT + ); + + return ( + + + + + + + + + + ); + } + )} + +
+ Qualification + + Worker + + { + onClickSortTableColumn( + "value_current", + state, + props.onChangeSortParam, + setCurrentSort + ); + }} + state={ + currentSort?.column === "value_current" + ? currentSort?.state + : SortArrowsState.INACTIVE + } + title={Current value} + /> + + { + onClickSortTableColumn( + "granted_at", + state, + props.onChangeSortParam, + setCurrentSort + ); + }} + state={ + currentSort?.column === "granted_at" + ? currentSort?.state + : SortArrowsState.INACTIVE + } + title={Updated} + /> + + Granted values +
+ + {gq.qualification_name} + + {gq.worker_name}{gq.value_current} + {grantedAtShort} + + {gq.units.map((unit: FGQUnit, index: number) => { + let valueAddition = ""; + if (unit.unit_id) { + const unitPageUrl = urls.client.taskUnit( + unit.task_id, + unit.unit_id + ); + valueAddition = unitPageUrl; + } else { + const creationDate = moment(unit.creation_date).format( + DEFAULT_DATE_FORMAT + ); + valueAddition = creationDate; + } + + const creationDateFull = moment( + unit.creation_date + ).format(DEFAULT_DATETIME_FORMAT); + + return ( + +
+ {unit.value}( + {unit.unit_id ? ( + + {unit.task_name} + + ) : ( + + {valueAddition} + + )} + ) +
+ +
+
+ ); + })} +
+ +
+ ); +} + +export default QualificationsTable; diff --git a/mephisto/review_app/client/src/pages/TasksPage/TasksPage.css b/mephisto/review_app/client/src/pages/TasksPage/TasksPage.css index e40a4676c..562954f4f 100644 --- a/mephisto/review_app/client/src/pages/TasksPage/TasksPage.css +++ b/mephisto/review_app/client/src/pages/TasksPage/TasksPage.css @@ -4,119 +4,5 @@ * LICENSE file in the root directory of this source tree. */ -.tasks .tasks-table { - width: 100%; -} - -.tasks .tasks-table .titles-row th { - background-color: #ecdadf; -} - -.tasks .tasks-table .titles-row .task { - width: 350px; - max-width: 350px; -} - -.tasks .tasks-table .titles-row .reviewed { - width: 80px; - text-align: center; -} - -.tasks .tasks-table .titles-row .units { - width: 80px; - text-align: center; -} - -.tasks .tasks-table .titles-row .date { - width: 130px; - text-align: center; -} - -.tasks .tasks-table .titles-row .stats { - width: 80px; - text-align: center; -} - -.tasks .tasks-table .titles-row .timeline { - width: 80px; - text-align: center; -} - -.tasks .tasks-table .titles-row .worker-opinions { - width: 80px; - text-align: center; -} - -.tasks .tasks-table .titles-row .display-units { - width: 115px; - text-align: center; - white-space: nowrap; -} - -.tasks .tasks-table .task-row:not(.no-hover) { - cursor: pointer; -} - -.tasks .tasks-table .task-row:not(.no-hover):hover td { - background-color: rgba(236, 218, 223, 0.3); -} - -.tasks .tasks-table .task-row .task { - width: 350px; - max-width: 350px; - text-align: left; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.tasks .tasks-table .task-row .task.text-primary { - cursor: pointer; -} -.tasks .tasks-table .task-row .task.text-primary:hover { - text-decoration: underline; -} - -.tasks .tasks-table .task-row .export-loading { - padding-left: 20px; -} - -.tasks .tasks-table .task-row .download-button { - cursor: pointer; -} -.tasks .tasks-table .task-row .download-button:hover { - text-decoration: underline; -} - -.tasks .tasks-table .task-row .stats a, -.tasks .tasks-table .task-row .timeline a, -.tasks .tasks-table .task-row .display-units a, -.tasks .tasks-table .task-row .worker-opinions a { - cursor: pointer; - text-decoration: unset; -} - -.tasks .tasks-table .task-row .stats a:hover, -.tasks .tasks-table .task-row .timeline a:hover, -.tasks .tasks-table .task-row .display-units a:hover, -.tasks .tasks-table .task-row .worker-opinions a:hover { - text-decoration: underline; -} - -.tasks .tasks-table .task-row .reviewed, -.tasks .tasks-table .task-row .units, -.tasks .tasks-table .task-row .stats, -.tasks .tasks-table .task-row .timeline, -.tasks .tasks-table .task-row .worker-opinions, -.tasks .tasks-table .task-row .display-units, -.tasks .tasks-table .task-row .date { - text-align: center; -} - -.tasks .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; +.tasks { } diff --git a/mephisto/review_app/client/src/pages/TasksPage/TasksPage.tsx b/mephisto/review_app/client/src/pages/TasksPage/TasksPage.tsx index 1388c98c4..4306f5984 100644 --- a/mephisto/review_app/client/src/pages/TasksPage/TasksPage.tsx +++ b/mephisto/review_app/client/src/pages/TasksPage/TasksPage.tsx @@ -4,233 +4,38 @@ * LICENSE file in the root directory of this source tree. */ +import Tabs from "components/Tabs/Tabs"; import TasksHeader from "components/TasksHeader/TasksHeader"; -import * as moment from "moment/moment"; import * as React from "react"; -import { useEffect } from "react"; -import { Spinner, Table } from "react-bootstrap"; -import { Link } from "react-router-dom"; -import { exportTaskResults, getTasks } from "requests/tasks"; -import urls from "urls"; +import QualificationsTab from "./QualificationsTab/QualificationsTab"; import "./TasksPage.css"; +import TasksTab from "./TasksTab/TasksTab"; -const STORAGE_TASK_ID_KEY: string = "selectedTaskID"; -const ENABLE_INCOMPLETE_TASK_RESULTS_EXPORT = true; - -interface TasksPagePropsType { +type TasksPagePropsType = { setErrors: Function; -} +}; function TasksPage(props: TasksPagePropsType) { - const { localStorage } = window; - - const [tasks, setTasks] = React.useState>(null); - const [loading, setLoading] = React.useState(false); - const [taskIdExportResults, setTaskIdExportResults] = React.useState(null); - const [loadingExportResults, setLoadingExportResults] = React.useState(false); - - function onTaskRowClick(id: string) { - localStorage.setItem(STORAGE_TASK_ID_KEY, String(id)); - - // Create a pseudo new link and click it to open a task in new tab (not window) - const pseudoLink = document.createElement("a"); - pseudoLink.setAttribute("href", urls.client.task(id)); - pseudoLink.setAttribute("target", "_blank"); - pseudoLink.click(); - } - - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } - - function requestTaskResults( - e: React.MouseEvent, - taskId: string, - nUnits: number - ) { - e.stopPropagation(); - - setTaskIdExportResults(taskId); - - function onSuccessExportResults(data) { - setTaskIdExportResults(null); - - if (data.file_created) { - // Create pseudo link and click it - const linkId = "result-json"; - const link = document.createElement("a"); - link.setAttribute("style", "display: none;"); - link.id = linkId; - link.href = urls.server.taskExportResultsJson(taskId, nUnits); - link.target = "_blank"; - link.click(); - link.remove(); - } - } - - exportTaskResults( - taskId, - onSuccessExportResults, - setLoadingExportResults, - onError - ); - } - - useEffect(() => { - document.title = "Mephisto - Task Review - All Tasks"; - - if (tasks === null) { - getTasks(setTasks, setLoading, onError, null); - } - }, []); + const tabs: TabType[] = [ + { + name: "tasks", + title: "Tasks", + children: , + noMargins: true, + }, + { + name: "worker_qualifications", + title: "Worker Qualifications", + children: , + noMargins: true, + }, + ]; return (
- {/* Header */} - {/* Tasks table */} - - - - - - - - - - - - - - - - - {tasks && - tasks.map((task: TaskType, index: number) => { - const date = moment(task.created_at).format("MMM D, YYYY"); - const nonClickable = - task.is_reviewed || task.unit_all_count === 0; - const allowTaskResultsDownload = - ENABLE_INCOMPLETE_TASK_RESULTS_EXPORT || task.is_reviewed; - - return ( - !nonClickable && onTaskRowClick(task.id)} - > - - - - - - - - - - - - ); - })} - -
- Task - - Reviewed? - - # Units - - Date - - Stats - - Timeline - - Opinions - - View Units - - Export results -
- {task.name} - - {task.is_reviewed ? : ""} - - {task.unit_finished_count}/{task.unit_all_count} - {date} - {task.has_stats && ( - - Show - - )} - - - Show - - - - Show - - - - Show - - - {allowTaskResultsDownload && - !( - loadingExportResults && taskIdExportResults === task.id - ) && ( - ) => - requestTaskResults( - e, - task.id, - task.unit_completed_count - ) - } - > - Download - - )} - - {taskIdExportResults === task.id && loadingExportResults && ( -
- - Loading... - -
- )} -
- - {/* Preloader when we request tasks */} - {loading && ( -
- - Loading... - -
- )} +
); } diff --git a/mephisto/review_app/client/src/pages/TasksPage/TasksTab/TasksTab.css b/mephisto/review_app/client/src/pages/TasksPage/TasksTab/TasksTab.css new file mode 100644 index 000000000..32d87d231 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/TasksTab/TasksTab.css @@ -0,0 +1,13 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.tasks-tab { + width: 100%; +} + +.tasks-tab .empty-message { + margin: 10px; +} diff --git a/mephisto/review_app/client/src/pages/TasksPage/TasksTab/TasksTab.tsx b/mephisto/review_app/client/src/pages/TasksPage/TasksTab/TasksTab.tsx new file mode 100644 index 000000000..d7929a1e6 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/TasksTab/TasksTab.tsx @@ -0,0 +1,50 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import Preloader from "components/Preloader/Preloader"; +import { setResponseErrors } from "helpers"; +import * as React from "react"; +import { useEffect } from "react"; +import { getTasks } from "requests/tasks"; +import TasksTable from "../TasksTable/TasksTable"; +import "./TasksTab.css"; + +interface TasksTabPropsType { + setErrors: Function; +} + +function TasksTab(props: TasksTabPropsType) { + const [tasks, setTasks] = React.useState(null); + const [loading, setLoading] = React.useState(false); + + const hasTasks = tasks && tasks.length !== 0; + + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); + + // Effects + useEffect(() => { + document.title = "Mephisto - Task Review - All Tasks"; + + if (tasks === null) { + getTasks(setTasks, setLoading, onError, null); + } + }, []); + + return ( +
+ {hasTasks ? ( + + ) : ( +
No available tasks yet.
+ )} + + +
+ ); +} + +export default TasksTab; diff --git a/mephisto/review_app/client/src/pages/TasksPage/TasksTable/TasksTable.css b/mephisto/review_app/client/src/pages/TasksPage/TasksTable/TasksTable.css new file mode 100644 index 000000000..470b4b4f6 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/TasksTable/TasksTable.css @@ -0,0 +1,114 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.tasks-table { + width: 100%; +} + +.tasks-table .titles-row th { + background-color: #ecdadf; +} + +.tasks-table .titles-row .task { + width: 350px; + max-width: 350px; +} + +.tasks-table .titles-row .reviewed { + width: 80px; + text-align: center; +} + +.tasks-table .titles-row .units { + width: 80px; + text-align: center; +} + +.tasks-table .titles-row .date { + width: 130px; + text-align: center; +} + +.tasks-table .titles-row .stats { + width: 80px; + text-align: center; +} + +.tasks-table .titles-row .timeline { + width: 80px; + text-align: center; +} + +.tasks-table .titles-row .worker-opinions { + width: 80px; + text-align: center; +} + +.tasks-table .titles-row .display-units { + width: 115px; + text-align: center; + white-space: nowrap; +} + +.tasks-table .value-row:not(.no-hover) { + cursor: pointer; +} + +.tasks-table .value-row:not(.no-hover):hover td { + background-color: rgba(236, 218, 223, 0.3); +} + +.tasks-table .value-row .task { + width: 350px; + max-width: 350px; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tasks-table .value-row .task.text-primary { + cursor: pointer; +} +.tasks-table .value-row .task.text-primary:hover { + text-decoration: underline; +} + +.tasks-table .value-row .export-loading { + padding-left: 20px; +} + +.tasks-table .value-row .download-button { + cursor: pointer; +} +.tasks-table .value-row .download-button:hover { + text-decoration: underline; +} + +.tasks-table .value-row .stats a, +.tasks-table .value-row .timeline a, +.tasks-table .value-row .display-units a, +.tasks-table .value-row .worker-opinions a { + cursor: pointer; + text-decoration: unset; +} + +.tasks-table .value-row .stats a:hover, +.tasks-table .value-row .timeline a:hover, +.tasks-table .value-row .display-units a:hover, +.tasks-table .value-row .worker-opinions a:hover { + text-decoration: underline; +} + +.tasks-table .value-row .reviewed, +.tasks-table .value-row .units, +.tasks-table .value-row .stats, +.tasks-table .value-row .timeline, +.tasks-table .value-row .worker-opinions, +.tasks-table .value-row .display-units, +.tasks-table .value-row .date { + text-align: center; +} diff --git a/mephisto/review_app/client/src/pages/TasksPage/TasksTable/TasksTable.tsx b/mephisto/review_app/client/src/pages/TasksPage/TasksTable/TasksTable.tsx new file mode 100644 index 000000000..d89b67132 --- /dev/null +++ b/mephisto/review_app/client/src/pages/TasksPage/TasksTable/TasksTable.tsx @@ -0,0 +1,208 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { DEFAULT_DATE_FORMAT } from "consts/format"; +import { setResponseErrors } from "helpers"; +import * as moment from "moment/moment"; +import * as React from "react"; +import { Spinner, Table } from "react-bootstrap"; +import { Link } from "react-router-dom"; +import { exportTaskResults } from "requests/tasks"; +import urls from "urls"; +import "./TasksTable.css"; + +const STORAGE_TASK_ID_KEY: string = "selectedTaskID"; +const ENABLE_INCOMPLETE_TASK_RESULTS_EXPORT = true; + +interface TasksTablePropsType { + setErrors: Function; + tasks: TaskType[]; +} + +function TasksTable(props: TasksTablePropsType) { + const { localStorage } = window; + + const [taskIdExportResults, setTaskIdExportResults] = React.useState(null); + const [loadingExportResults, setLoadingExportResults] = React.useState(false); + + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); + + function onTaskRowClick(id: string) { + localStorage.setItem(STORAGE_TASK_ID_KEY, String(id)); + + // Create a pseudo new link and click it to open a task in new tab (not window) + const pseudoLink = document.createElement("a"); + pseudoLink.setAttribute("href", urls.client.task(id)); + pseudoLink.setAttribute("target", "_blank"); + pseudoLink.click(); + } + + function requestTaskResults( + e: React.MouseEvent, + taskId: string, + nUnits: number + ) { + e.stopPropagation(); + + setTaskIdExportResults(taskId); + + function onSuccessExportResults(data) { + setTaskIdExportResults(null); + + if (data.file_created) { + // Create pseudo link and click it + const linkId = "result-json"; + const link = document.createElement("a"); + link.setAttribute("style", "display: none;"); + link.id = linkId; + link.href = urls.server.taskExportResultsJson(taskId, nUnits); + link.target = "_blank"; + link.click(); + link.remove(); + } + } + + exportTaskResults( + taskId, + onSuccessExportResults, + setLoadingExportResults, + onError + ); + } + + return ( + + + + + + + + + + + + + + + + + + {props.tasks && + props.tasks.map((task: TaskType, index: number) => { + const date = moment(task.created_at).format(DEFAULT_DATE_FORMAT); + const nonClickable = task.is_reviewed || task.unit_all_count === 0; + const allowTaskResultsDownload = + ENABLE_INCOMPLETE_TASK_RESULTS_EXPORT || task.is_reviewed; + + return ( + !nonClickable && onTaskRowClick(task.id)} + > + + + + + + + + + + + + ); + })} + +
+ Task + + Reviewed? + + # Units + + Date + + Stats + + Timeline + + Opinions + + View Units + + Export results +
+ {task.name} + + {task.is_reviewed ? : ""} + + {task.unit_finished_count}/{task.unit_all_count} + {date} + {task.has_stats && ( + + Show + + )} + + + Show + + + + Show + + + + Show + + + {allowTaskResultsDownload && + !( + loadingExportResults && taskIdExportResults === task.id + ) && ( + ) => + requestTaskResults( + e, + task.id, + task.unit_completed_count + ) + } + > + Download + + )} + + {taskIdExportResults === task.id && loadingExportResults && ( +
+ + Loading... + +
+ )} +
+ ); +} + +export default TasksTable; diff --git a/mephisto/review_app/client/src/pages/UnitPage/UnitPage.css b/mephisto/review_app/client/src/pages/UnitPage/UnitPage.css index 9e878ca89..0f9a96f01 100644 --- a/mephisto/review_app/client/src/pages/UnitPage/UnitPage.css +++ b/mephisto/review_app/client/src/pages/UnitPage/UnitPage.css @@ -37,14 +37,6 @@ cursor: default; } -.unit .loading { - width: 100%; - height: 100px; - display: flex; - align-items: center; - justify-content: center; -} - .unit-preview-iframe { width: 100%; } diff --git a/mephisto/review_app/client/src/pages/UnitPage/UnitPage.tsx b/mephisto/review_app/client/src/pages/UnitPage/UnitPage.tsx index aa29b8e16..fec835457 100644 --- a/mephisto/review_app/client/src/pages/UnitPage/UnitPage.tsx +++ b/mephisto/review_app/client/src/pages/UnitPage/UnitPage.tsx @@ -6,6 +6,7 @@ import InitialParametersCollapsable from "components/InitialParametersCollapsable/InitialParametersCollapsable"; import { InReviewFileModal } from "components/InReviewFileModal/InReviewFileModal"; +import Preloader from "components/Preloader/Preloader"; import ResultsCollapsable from "components/ResultsCollapsable/ResultsCollapsable"; import TasksHeader from "components/TasksHeader/TasksHeader"; import VideoAnnotatorWebVTTCollapsable from "components/VideoAnnotatorWebVTTCollapsable/VideoAnnotatorWebVTTCollapsable"; @@ -14,15 +15,15 @@ import { MESSAGES_IFRAME_DATA_KEY, MESSAGES_IN_REVIEW_FILE_DATA_KEY, } from "consts/review"; -import { setPageTitle } from "helpers"; +import { setPageTitle, setResponseErrors } from "helpers"; import * as React from "react"; import { useEffect } from "react"; -import { Spinner } from "react-bootstrap"; import { useParams } from "react-router-dom"; import { getTask } from "requests/tasks"; import { getUnits, getUnitsDetails } from "requests/units"; import urls from "urls"; import "./UnitPage.css"; +import UnitReviewsCollapsable from "./UnitReviewsCollapsable/UnitReviewsCollapsable"; type ParamsType = { taskId: string; @@ -118,11 +119,8 @@ function UnitPage(props: UnitPagePropsType) { setInReviewFileModalShow(true); } - function onError(errorResponse: ErrorResponseType | null) { - if (errorResponse) { - props.setErrors((oldErrors) => [...oldErrors, ...[errorResponse.error]]); - } - } + const onError = (response: ErrorResponseType) => + setResponseErrors(props.setErrors, response); // [RECEIVING WIDGET DATA] // --- @@ -222,13 +220,7 @@ function UnitPage(props: UnitPagePropsType) {
{/* Preloader when we request unit */} - {loading && ( -
- - Loading... - -
- )} + {/* Initial Unit parameters */} {unitDetails?.inputs && ( @@ -255,6 +247,14 @@ function UnitPage(props: UnitPagePropsType) { /> )} + {/* Review history of Unit */} + {unitDetails?.metadata?.worker_reviews && ( + + )} + {unitDetails?.outputs && ( <> {/* Results */} diff --git a/mephisto/review_app/client/src/pages/UnitPage/UnitReviewsCollapsable/UnitReviewsCollapsable.css b/mephisto/review_app/client/src/pages/UnitPage/UnitReviewsCollapsable/UnitReviewsCollapsable.css new file mode 100644 index 000000000..012398f75 --- /dev/null +++ b/mephisto/review_app/client/src/pages/UnitPage/UnitReviewsCollapsable/UnitReviewsCollapsable.css @@ -0,0 +1,61 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.unit-reviews { +} + +.unit-reviews .unit-reviews-table .titles-row th { + background-color: #ecdadf; +} + +.unit-reviews .unit-reviews-table .titles-row .value, +.unit-reviews .unit-reviews-table .titles-row .status, +.unit-reviews .unit-reviews-table .titles-row .blocked, +.unit-reviews .unit-reviews-table .titles-row .bonus { + width: 80px; + text-align: center; + white-space: nowrap; +} + +.unit-reviews .unit-reviews-table .titles-row .qualification { + width: 350px; + max-width: 350px; +} + +.unit-reviews .unit-reviews-table .titles-row .date { + width: 130px; + text-align: center; + white-space: nowrap; +} + +.unit-reviews .unit-reviews-table .titles-row .note { + width: 350px; + max-width: 350px; + text-align: center; + white-space: nowrap; +} + +.unit-reviews .unit-reviews-table .value-row .value, +.unit-reviews .unit-reviews-table .value-row .date, +.unit-reviews .unit-reviews-table .value-row .status, +.unit-reviews .unit-reviews-table .value-row .blocked, +.unit-reviews .unit-reviews-table .value-row .bonus { + text-align: center; +} + +.unit-reviews .unit-reviews-table .value-row .qualification { + width: 350px; + max-width: 350px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.unit-reviews .unit-reviews-table .value-row .note { + width: 350px; + max-width: 350px; + text-align: left; +} diff --git a/mephisto/review_app/client/src/pages/UnitPage/UnitReviewsCollapsable/UnitReviewsCollapsable.tsx b/mephisto/review_app/client/src/pages/UnitPage/UnitReviewsCollapsable/UnitReviewsCollapsable.tsx new file mode 100644 index 000000000..11dd6fbcf --- /dev/null +++ b/mephisto/review_app/client/src/pages/UnitPage/UnitReviewsCollapsable/UnitReviewsCollapsable.tsx @@ -0,0 +1,106 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import CollapsableBlock from "components/CollapsableBlock/CollapsableBlock"; +import { DEFAULT_DATE_FORMAT } from "consts/format"; +import { STATUS_COLOR_CLASS_MAPPING } from "consts/review"; +import { capitalizeString } from "helpers"; +import * as moment from "moment"; +import * as React from "react"; +import { Table } from "react-bootstrap"; +import "./UnitReviewsCollapsable.css"; + +type UnitReviewsCollapsablePropsType = { + className?: string; + unitReviews: WorkerReviewType[]; + open?: boolean; + title?: string | React.ReactElement; +}; + +function UnitReviewsCollapsable(props: UnitReviewsCollapsablePropsType) { + const { className, open, title, unitReviews } = props; + + const _title = title || "Granted Qualifications"; + + return ( + + + + + + + + + + + + + + + + {(unitReviews || [].length) && + unitReviews.map((unitReview: WorkerReviewType, index: number) => { + const date = moment(unitReview.creation_date).format( + DEFAULT_DATE_FORMAT + ); + const statusColorClass = + STATUS_COLOR_CLASS_MAPPING[unitReview.status]; + + return ( + + + + + + + + + + + + ); + })} + +
+ Qualification + + Value + + Date + + Unit Action + + Bonus + + Worker Action + + Note +
+ {unitReview.qualification_name} + {unitReview.value}{date} + {capitalizeString(unitReview.status.replace("_", "-"))} + {unitReview.bonus} + {unitReview.blocked_worker ? BLOCKED : ""} + {unitReview.review_note}
+
+ ); +} + +export default UnitReviewsCollapsable; diff --git a/mephisto/review_app/client/src/requests/qualifications.ts b/mephisto/review_app/client/src/requests/qualifications.ts index 70e5a9b05..ed3affaf5 100644 --- a/mephisto/review_app/client/src/requests/qualifications.ts +++ b/mephisto/review_app/client/src/requests/qualifications.ts @@ -24,24 +24,62 @@ export function getQualifications( (data) => setDataAction(data.qualifications), setLoadingAction, setErrorsAction, - "getTasks error:", + "getQualifications error:", abortController ); } -export function getQualificationWorkers( +export function getQualification( id: string, setDataAction: SetRequestDataActionType, setLoadingAction: SetRequestLoadingActionType, setErrorsAction: SetRequestErrorsActionType, - getParams: { [key: string]: string | number } = null, abortController?: AbortController ) { - const url = generateURL( - urls.server.qualificationWorkers(id), + const url = generateURL(urls.server.qualification, [id], null); + + makeRequest( + "GET", + url, + null, + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "getQualification error:", + abortController + ); +} + +export function getQualificationDetails( + id: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + abortController?: AbortController +) { + const url = generateURL(urls.server.qualificationDetails, [id], null); + + makeRequest( + "GET", + url, null, - getParams + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "getQualificationDetails error:", + abortController ); +} + +export function getQualificationWorkers( + id: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + getParams: { [key: string]: string | number } = null, + abortController?: AbortController +) { + const url = generateURL(urls.server.qualificationWorkers, [id], getParams); makeRequest( "GET", @@ -76,8 +114,51 @@ export function postQualification( ); } -export function postQualificationGrantWorker( +export function patchQualification( + id: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + data: { [key: string]: string | number }, + abortController?: AbortController +) { + const url = generateURL(urls.server.qualification, [id], null); + + makeRequest( + "PATCH", + url, + JSON.stringify(data), + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "patchQualification error:", + abortController + ); +} + +export function deleteQualification( id: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + abortController?: AbortController +) { + const url = generateURL(urls.server.qualification, [id], null); + + makeRequest( + "DELETE", + url, + null, + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "deleteQualification error:", + abortController + ); +} + +export function postQualificationGrantWorker( + qualificationId: string, workerId: string, setDataAction: SetRequestDataActionType, setLoadingAction: SetRequestLoadingActionType, @@ -86,8 +167,8 @@ export function postQualificationGrantWorker( abortController?: AbortController ) { const url = generateURL( - urls.server.qualificationGrantWorker(id, workerId), - null, + urls.server.qualificationGrantWorker, + [qualificationId, workerId], null ); @@ -104,7 +185,7 @@ export function postQualificationGrantWorker( } export function postQualificationRevokeWorker( - id: string, + qualificationId: string, workerId: string, setDataAction: SetRequestDataActionType, setLoadingAction: SetRequestLoadingActionType, @@ -113,8 +194,8 @@ export function postQualificationRevokeWorker( abortController?: AbortController ) { const url = generateURL( - urls.server.qualificationRevokeWorker(id, workerId), - null, + urls.server.qualificationRevokeWorker, + [qualificationId, workerId], null ); @@ -129,3 +210,77 @@ export function postQualificationRevokeWorker( abortController ); } + +export function patchQualificationGrantWorker( + quailificationId: string, + workerId: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + data: { [key: string]: string[] | number[] | number | string }, + abortController?: AbortController +) { + const url = generateURL( + urls.server.qualificationGrantWorker, + [quailificationId, workerId], + null + ); + + makeRequest( + "PATCH", + url, + JSON.stringify(data), + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "patchQualificationGrantWorker error:", + abortController + ); +} + +export function patchQualificationRevokeWorker( + quailificationId: string, + workerId: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + abortController?: AbortController +) { + const url = generateURL( + urls.server.qualificationRevokeWorker, + [quailificationId, workerId], + null + ); + + makeRequest( + "PATCH", + url, + "{}", + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "patchQualificationRevokeWorker error:", + abortController + ); +} + +export function getGrantedQualifications( + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + getParams: { [key: string]: string | number } = null, + abortController?: AbortController +) { + const url = generateURL(urls.server.grantedQualifications, null, getParams); + + makeRequest( + "GET", + url, + null, + (data) => setDataAction(data.granted_qualifications), + setLoadingAction, + setErrorsAction, + "getGrantedQualifications error:", + abortController + ); +} diff --git a/mephisto/review_app/client/src/requests/workers.ts b/mephisto/review_app/client/src/requests/workers.ts index db2921dce..1d0c526fb 100644 --- a/mephisto/review_app/client/src/requests/workers.ts +++ b/mephisto/review_app/client/src/requests/workers.ts @@ -50,3 +50,27 @@ export function getWorkerGrantedQualifications( abortController ); } + +export function postWorkerGrant( + id: string, + setDataAction: SetRequestDataActionType, + setLoadingAction: SetRequestLoadingActionType, + setErrorsAction: SetRequestErrorsActionType, + data: { + [key: string]: string | number | string[] | FormSelectedQualificationType[]; + }, + abortController?: AbortController +) { + const url = generateURL(urls.server.workerGrant, [id], null); + + makeRequest( + "POST", + url, + JSON.stringify(data), + (data) => setDataAction(data), + setLoadingAction, + setErrorsAction, + "postWorkerGrant error:", + abortController + ); +} diff --git a/mephisto/review_app/client/src/types/qualifications.d.ts b/mephisto/review_app/client/src/types/qualifications.d.ts index 1d04771c2..c6a26bd5e 100644 --- a/mephisto/review_app/client/src/types/qualifications.d.ts +++ b/mephisto/review_app/client/src/types/qualifications.d.ts @@ -5,17 +5,56 @@ */ declare type QualificationType = { + creation_date: string; + description: string; id: string; name: string; }; +declare type QualificationDetailsType = { + granted_qualifications_count: number; +}; + declare type GrantedQualificationType = { - worker_id: string; + granted_at: string; qualification_id: number; value: number; - granted_at: string; + worker_id: string; }; declare type WorkerGrantedQualificationsType = { [key: string]: GrantedQualificationType; }; + +declare type SelectedQualificationType = { + qualification_id: string; + qualification_name: string; + value: number; +}; + +declare type SelectedQualificationsType = { + [key: string]: SelectedQualificationType; +}; + +declare type FGQUnit = { + creation_date: string; + task_id: string; + task_name: string; + unit_id: string; + value: string; +}; + +declare type FullGrantedQualificationType = { + granted_at: string; + qualification_id: string; + qualification_name: string; + units: FGQUnit[]; + value_current: number; + worker_id: string; + worker_name: string; +}; + +declare type CreateQualificationFormType = { + description: string; + name: string; +}; diff --git a/mephisto/review_app/client/src/types/reviewModal.d.ts b/mephisto/review_app/client/src/types/reviewModal.d.ts index c88b4e859..49157ce49 100644 --- a/mephisto/review_app/client/src/types/reviewModal.d.ts +++ b/mephisto/review_app/client/src/types/reviewModal.d.ts @@ -4,6 +4,11 @@ * LICENSE file in the root directory of this source tree. */ +declare type FormSelectedQualificationType = { + qualification_id: string; + value: number; +}; + type FormType = { bonus: number | null; checkboxAssignQualification?: boolean; @@ -12,10 +17,12 @@ type FormType = { checkboxReviewNote: boolean; checkboxReviewNoteSend?: boolean; checkboxUnassignQualification?: boolean; - newQualificationValue?: string; - qualification: string | null; - qualificationValue: number; + newQualificationDescription?: string; + newQualificationName?: string; reviewNote: string; + selectQualification: string | null; + selectQualificationValue: number; + selectedQualifications?: FormSelectedQualificationType[]; showNewQualification?: boolean; }; diff --git a/mephisto/review_app/client/src/types/tabs.d.ts b/mephisto/review_app/client/src/types/tabs.d.ts new file mode 100644 index 000000000..758524d08 --- /dev/null +++ b/mephisto/review_app/client/src/types/tabs.d.ts @@ -0,0 +1,15 @@ +/* + * Copyright (c) Meta Platforms and its affiliates. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +declare type TabType = { + name: string; + title: string; + hoverText?: string; + hoverTextDisabled?: string; + children?: React.ReactNode | string; + noMargins?: boolean; + disabled?: boolean; +}; diff --git a/mephisto/review_app/client/src/types/units.d.ts b/mephisto/review_app/client/src/types/units.d.ts index f1aa1b0b7..254f9c735 100644 --- a/mephisto/review_app/client/src/types/units.d.ts +++ b/mephisto/review_app/client/src/types/units.d.ts @@ -55,6 +55,7 @@ declare type WorkerOpinionType = { declare type UnitDetailsMetadataType = { worker_opinion?: WorkerOpinionType; webvtt?: string; + worker_reviews: WorkerReviewType[]; }; declare type UnitDetailsType = { @@ -66,3 +67,14 @@ declare type UnitDetailsType = { prepared_inputs: object; unit_data_folder: string; }; + +declare type WorkerReviewType = { + blocked_worker: number; + bonus: number; + creation_date: string; + qualification_id: string; + qualification_name: string; + review_note: string; + status: string; + value: number; +}; diff --git a/mephisto/review_app/client/src/urls.ts b/mephisto/review_app/client/src/urls.ts index 579e86022..48d7afa7e 100644 --- a/mephisto/review_app/client/src/urls.ts +++ b/mephisto/review_app/client/src/urls.ts @@ -9,6 +9,7 @@ const API_URL = process.env.REACT_APP__API_URL || ""; const urls = { client: { home: "/", + qualification: (id) => `/qualifications/${id}`, task: (id) => `/tasks/${id}`, taskStats: (id) => `/tasks/${id}/stats`, taskTimeline: (id) => `/tasks/${id}/timeline`, @@ -18,6 +19,9 @@ const urls = { tasks: "/tasks", }, server: { + grantedQualifications: API_URL + "/api/granted-qualifications", + qualification: (id) => API_URL + `/api/qualifications/${id}`, + qualificationDetails: (id) => API_URL + `/api/qualifications/${id}/details`, qualifications: API_URL + "/api/qualifications", qualificationWorkers: (id) => API_URL + `/api/qualifications/${id}/workers`, qualificationGrantWorker: (id, workerId) => @@ -44,6 +48,7 @@ const urls = { API_URL + `/api/units/${id}/static/${filename}`, unitsOutputsFileByFieldname: (id, fieldname) => API_URL + `/api/units/${id}/static/fieldname/${fieldname}`, + workerGrant: (id) => API_URL + `/api/workers/${id}/qualifications/grant`, workerGrantedQualifications: (id) => API_URL + `/api/workers/${id}/qualifications`, workersBlock: (id) => API_URL + `/api/workers/${id}/block`, diff --git a/mephisto/review_app/server/api/views/__init__.py b/mephisto/review_app/server/api/views/__init__.py index 758122a88..0f237c942 100644 --- a/mephisto/review_app/server/api/views/__init__.py +++ b/mephisto/review_app/server/api/views/__init__.py @@ -4,7 +4,10 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +from .granted_qualifications_view import GrantedQualificationsView from .home_view import HomeView +from .qualification_details_view import QualificationDetailsView +from .qualification_view import QualificationView from .qualification_workers_view import QualificationWorkersView from .qualifications_view import QualificationsView from .qualify_worker_view import QualifyWorkerView @@ -28,3 +31,4 @@ from .units_view import UnitsView from .worker_block_view import WorkerBlockView from .worker_granted_qualifications_view import WorkerGrantedQualificationsView +from .worker_qualifications_grant_view import WorkerQualificationsGrantView diff --git a/mephisto/review_app/server/api/views/granted_qualifications_view.py b/mephisto/review_app/server/api/views/granted_qualifications_view.py new file mode 100644 index 000000000..eeb2a87bb --- /dev/null +++ b/mephisto/review_app/server/api/views/granted_qualifications_view.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 + +# Copyright (c) Meta Platforms and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List +from typing import Optional + +from flask import current_app as app +from flask import request +from flask.views import MethodView + +from mephisto.abstractions.databases.local_database import LocalMephistoDB +from mephisto.abstractions.databases.local_database import nonesafe_int +from mephisto.abstractions.databases.local_database import StringIDRow +from mephisto.data_model.constants.assignment_state import AssignmentState + +LIMIT_UNITS_FOR_QUALIFICATION = 3 +STATUSES_UNITS_FOR_QUALIFICATION = AssignmentState.completed() + + +def _find_granted_qualifications( + db: LocalMephistoDB, + qualification_id: Optional[str] = None, + sort_param: Optional[str] = None, +) -> List[StringIDRow]: + """Return the granted qualifications in the database""" + + with db.table_access_condition: + conn = db.get_connection() + c = conn.cursor() + + params = [] + + # Exclude granted qualifications for blocked workers + blocked_worker_query = "blocked_worker IS NULL" + + qualification_query = "gq.qualification_id = ?1" if qualification_id else "" + if qualification_id is not None: + params.append(nonesafe_int(qualification_id)) + + joined_queries = " AND ".join( + list( + filter( + bool, + [ + blocked_worker_query, + qualification_query, + ], + ) + ) + ) + + where_query = f"WHERE {joined_queries}" if joined_queries else "" + + order_by_query = "" + if sort_param: + order_direction = "DESC" if sort_param.startswith("-") else "ASC" + order_column = sort_param[1:] if sort_param.startswith("-") else sort_param + order_by_query = f"ORDER BY {order_column} {order_direction}" + + c.execute( + f""" + SELECT + gq.qualification_id AS qualification_id, + q.qualification_name AS qualification_name, + gq.worker_id AS worker_id, + w.worker_name AS worker_name, + gq.value AS value_current, + gq.update_date AS granted_at, + wr.blocked_worker AS blocked_worker + FROM granted_qualifications AS gq + LEFT JOIN ( + SELECT + worker_id, + worker_name, + creation_date + FROM workers + ) AS w ON w.worker_id = gq.worker_id + LEFT JOIN ( + SELECT + qualification_id, + qualification_name, + creation_date + FROM qualifications + ) AS q ON q.qualification_id = gq.qualification_id + LEFT JOIN ( + SELECT + id, + blocked_worker, + updated_qualification_id, + revoked_qualification_id, + worker_id, + creation_date + FROM worker_review + WHERE blocked_worker = 1 + ) AS wr ON ( + wr.worker_id = gq.worker_id AND ( + ( + wr.updated_qualification_id = gq.qualification_id AND + wr.revoked_qualification_id IS NULL + ) + OR + ( + wr.revoked_qualification_id = gq.qualification_id AND + wr.updated_qualification_id IS NULL + ) + ) + ) + {where_query} + {order_by_query}; + """, + params, + ) + rows = c.fetchall() + return rows + + +def _find_grants( + db: LocalMephistoDB, + worker_id: str, + qualification_id: str, + statuses: Optional[List[str]] = None, + limit: Optional[int] = None, +) -> List[StringIDRow]: + """Return the units for granted qualification""" + + with db.table_access_condition: + conn = db.get_connection() + c = conn.cursor() + + params = [ + nonesafe_int(worker_id), + nonesafe_int(qualification_id), + ] + + worker_query = "wr.worker_id = ?1" + + qualification_query = "wr.updated_qualification_id = ?2" + + units_statuses_string = ",".join([f"'{s}'" for s in statuses]) + status_query = f"status IN ({units_statuses_string})" if statuses else "" + + joined_queries = " AND ".join( + list( + filter( + bool, + [ + worker_query, + qualification_query, + ], + ) + ) + ) + + where_query = f"WHERE {joined_queries}" if joined_queries else "" + + limit_query = "LIMIT ?3" if limit else "" + if limit: + params.append(nonesafe_int(limit)) + + c.execute( + f""" + SELECT + wr.task_id as task_id, + t.task_name as task_name, + wr.unit_id as unit_id, + wr.updated_qualification_value as updated_qualification_value, + wr.creation_date as creation_date + FROM worker_review AS wr + LEFT JOIN ( + SELECT + task_id, + task_name + FROM tasks + ) AS t ON t.task_id = wr.task_id + LEFT JOIN ( + SELECT + unit_id, + status + FROM units + WHERE {status_query} + ORDER BY creation_date DESC + ) AS u ON u.unit_id = wr.unit_id + {where_query} + ORDER BY creation_date DESC + {limit_query}; + """, + params, + ) + rows = c.fetchall() + return rows + + +class GrantedQualificationsView(MethodView): + def get(self) -> dict: + """Get list of all granted queslifications.""" + + qualification_id_param = request.args.get("qualification_id") + sort_param = request.args.get("sort") + + db_granted_qualifications = _find_granted_qualifications( + db=app.db, + qualification_id=qualification_id_param, + sort_param=sort_param, + ) + + app.logger.debug(f"Found granted qualifications in DB: {list(db_granted_qualifications)}") + + granted_qualifications = [] + for gq in db_granted_qualifications: + units = [ + { + "creation_date": u["creation_date"], + "task_id": u["task_id"], + "task_name": u["task_name"], + "unit_id": u["unit_id"], + "value": u["updated_qualification_value"], + } + for u in _find_grants( + db=app.db, + worker_id=gq["worker_id"], + qualification_id=gq["qualification_id"], + statuses=STATUSES_UNITS_FOR_QUALIFICATION, + limit=LIMIT_UNITS_FOR_QUALIFICATION, + ) + ] + granted_qualifications.append( + { + "granted_at": gq["granted_at"], + "qualification_id": gq["qualification_id"], + "qualification_name": gq["qualification_name"], + "units": units, + "value_current": gq["value_current"], + "worker_id": gq["worker_id"], + "worker_name": gq["worker_name"], + }, + ) + + return { + "granted_qualifications": granted_qualifications, + } diff --git a/mephisto/review_app/server/api/views/qualification_details_view.py b/mephisto/review_app/server/api/views/qualification_details_view.py new file mode 100644 index 000000000..929c155d1 --- /dev/null +++ b/mephisto/review_app/server/api/views/qualification_details_view.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +# Copyright (c) Meta Platforms and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from flask import current_app as app +from flask.views import MethodView + +from mephisto.abstractions.databases.local_database import StringIDRow + + +class QualificationDetailsView(MethodView): + def get(self, qualification_id: str = None) -> dict: + """Get qualification details""" + + db_qualification: StringIDRow = app.db.get_qualification(qualification_id) + app.logger.debug(f"Found Qualification in DB: {db_qualification}") + + db_granted_qualifications: StringIDRow = app.db.find_granted_qualifications( + qualification_id=qualification_id + ) + + return { + "granted_qualifications_count": len(db_granted_qualifications), + } diff --git a/mephisto/review_app/server/api/views/qualification_view.py b/mephisto/review_app/server/api/views/qualification_view.py new file mode 100644 index 000000000..a93fd7a69 --- /dev/null +++ b/mephisto/review_app/server/api/views/qualification_view.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +# Copyright (c) Meta Platforms and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Tuple + +from flask import current_app as app +from flask import request +from flask.views import MethodView +from werkzeug.exceptions import BadRequest + +from mephisto.abstractions.databases.local_database import StringIDRow + + +class QualificationView(MethodView): + def get(self, qualification_id: str = None) -> dict: + """Get qualification""" + + db_qualification: StringIDRow = app.db.get_qualification(qualification_id) + app.logger.debug(f"Found Qualification in DB: {db_qualification}") + + return { + "creation_date": db_qualification["creation_date"], + "description": db_qualification["description"], + "id": db_qualification["qualification_id"], + "name": db_qualification["qualification_name"], + } + + def patch(self, qualification_id: str = None) -> dict: + """Update qualification""" + + db_qualification: StringIDRow = app.db.get_qualification(qualification_id) + app.logger.debug(f"Found Qualification in DB: {db_qualification}") + + data: dict = request.json or {} + name: str = data.get("name") + description: str = data.get("description") + + if not name: + raise BadRequest('Field "name" is required.') + + name = name.strip() + description = description.strip() if description else None + + app.db.update_qualification( + qualification_id=qualification_id, + name=name, + description=description, + ) + + updated_qualification: StringIDRow = app.db.get_qualification(qualification_id) + + return { + "creation_date": updated_qualification["creation_date"], + "description": updated_qualification["description"], + "id": updated_qualification["qualification_id"], + "name": updated_qualification["qualification_name"], + } + + def delete(self, qualification_id: str = None) -> dict: + """Delete qualification""" + + db_qualification: StringIDRow = app.db.get_qualification(qualification_id) + app.logger.debug(f"Found Qualification in DB: {db_qualification}") + + app.db.delete_qualification(qualification_name=db_qualification["qualification_name"]) + + return {} diff --git a/mephisto/review_app/server/api/views/qualification_workers_view.py b/mephisto/review_app/server/api/views/qualification_workers_view.py index 21e23f1be..d33f0fef3 100644 --- a/mephisto/review_app/server/api/views/qualification_workers_view.py +++ b/mephisto/review_app/server/api/views/qualification_workers_view.py @@ -34,7 +34,7 @@ def _find_granted_qualifications(db: LocalMephistoDB, qualification_id: str) -> return results -def _find_unit_reviews( +def _find_worker_reviews( db, qualification_id: str, worker_id: str, @@ -54,7 +54,7 @@ def _find_unit_reviews( c = conn.cursor() c.execute( f""" - SELECT * FROM unit_review + SELECT * FROM worker_review WHERE (updated_qualification_id = ?1) AND (worker_id = ?2) {task_query} ORDER BY creation_date ASC; """, @@ -84,12 +84,17 @@ def get(self, qualification_id: int) -> dict: workers = [] for gq in db_granted_qualifications: - unit_reviews = _find_unit_reviews(app.db, qualification_id, gq["worker_id"], task_id) + worker_reviews = _find_worker_reviews( + app.db, + qualification_id, + gq["worker_id"], + task_id, + ) - if unit_reviews: - latest_unit_review = unit_reviews[-1] - unit_review_id = latest_unit_review["id"] - granted_at = latest_unit_review["creation_date"] + if worker_reviews: + latest_worker_review = worker_reviews[-1] + worker_review_id = latest_worker_review["id"] + granted_at = latest_worker_review["creation_date"] else: continue @@ -97,8 +102,8 @@ def get(self, qualification_id: int) -> dict: { "worker_id": gq["worker_id"], "value": gq["value"], - "unit_review_id": unit_review_id, # latest grant of this qualification - "granted_at": granted_at, # maps to `unit_review.creation_date` column + "worker_review_id": worker_review_id, # latest grant of this qualification + "granted_at": granted_at, # maps to `worker_review.creation_date` column } ) diff --git a/mephisto/review_app/server/api/views/qualifications_view.py b/mephisto/review_app/server/api/views/qualifications_view.py index 601559fb7..8cc5d1a01 100644 --- a/mephisto/review_app/server/api/views/qualifications_view.py +++ b/mephisto/review_app/server/api/views/qualifications_view.py @@ -71,6 +71,8 @@ def get(self) -> dict: qualifications = [ { + "creation_date": q.creation_date, + "description": q.description, "id": q.db_id, "name": q.qualification_name, } @@ -86,8 +88,12 @@ def get(self) -> dict: def post(self) -> dict: """Create a new qualification""" - data: dict = request.json - qualification_name = data and data.get("name") + data: dict = request.json or {} + qualification_name = data.get("name") + qualification_description = data.get("description") + + if qualification_description: + qualification_description = qualification_description[:500] if not qualification_name: raise BadRequest('Field "name" is required.') @@ -97,10 +103,15 @@ def post(self) -> dict: if db_qualifications: raise BadRequest(f'Qualification with name "{qualification_name}" already exists.') - db_qualification_id: str = app.db.make_qualification(qualification_name) + db_qualification_id: str = app.db.make_qualification( + qualification_name, + qualification_description, + ) db_qualification: StringIDRow = app.db.get_qualification(db_qualification_id) return { + "creation_date": db_qualification["creation_date"], + "description": db_qualification["description"], "id": db_qualification["qualification_id"], "name": db_qualification["qualification_name"], } diff --git a/mephisto/review_app/server/api/views/qualify_worker_view.py b/mephisto/review_app/server/api/views/qualify_worker_view.py index 0656d7faf..2ad1fe985 100644 --- a/mephisto/review_app/server/api/views/qualify_worker_view.py +++ b/mephisto/review_app/server/api/views/qualify_worker_view.py @@ -4,6 +4,7 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +from typing import List from typing import Optional from flask import current_app as app @@ -11,34 +12,54 @@ from flask.views import MethodView from werkzeug.exceptions import BadRequest +from mephisto.abstractions.databases.local_database import LocalMephistoDB +from mephisto.abstractions.databases.local_database import nonesafe_int from mephisto.abstractions.databases.local_database import StringIDRow from mephisto.data_model.unit import Unit from mephisto.data_model.worker import Worker -def _write_grant_unit_review( - db, - unit_id: int, - qualification_id: int, - worker_id: int, - value: Optional[int] = None, -): - db.update_unit_review(unit_id, qualification_id, worker_id, value, revoke=False) +class UpdateGrantedQualificationStatus: + GRANT = "grant" + REVOKE = "revoke" -def _write_revoke_unit_review( - db, - unit_id: int, - qualification_id: int, +def _find_units_ids( + db: LocalMephistoDB, worker_id: int, - value: Optional[int] = None, -): - db.update_unit_review(unit_id, qualification_id, worker_id, value, revoke=True) + qualification_id: int, +) -> List[str]: + """Return the units for granted qualification""" + + with db.table_access_condition: + conn = db.get_connection() + c = conn.cursor() + + params = [ + nonesafe_int(qualification_id), + nonesafe_int(worker_id), + ] + + c.execute( + f""" + SELECT + wr.unit_id as unit_id + FROM worker_review AS wr + WHERE ( + wr.worker_id = ?2 AND + (wr.updated_qualification_id = ?1 OR wr.revoked_qualification_id = ?1) + ); + """, + params, + ) + rows = c.fetchall() + unit_ids = list(set([u["unit_id"] for u in rows])) + return unit_ids class QualifyWorkerView(MethodView): @staticmethod - def _grant_worker_qualification( + def grant_worker_qualification_with_unit( qualification: StringIDRow, unit: Unit, worker: Worker, @@ -46,31 +67,71 @@ def _grant_worker_qualification( ): worker.grant_qualification(qualification["qualification_name"], value) - _write_grant_unit_review( - app.db, - int(unit.db_id), - qualification["qualification_id"], - int(worker.db_id), - value, + app.db.update_worker_review( + unit_id=unit.db_id, + qualification_id=qualification["qualification_id"], + worker_id=worker.db_id, + value=value, + revoke=False, ) @staticmethod - def _revoke_worker_qualification(qualification: StringIDRow, unit: Unit, worker: Worker): + def _revoke_worker_qualification_with_unit( + qualification: StringIDRow, + unit: Unit, + worker: Worker, + value: Optional[int] = None, + ): worker.revoke_qualification(qualification["qualification_name"]) - _write_revoke_unit_review( - app.db, - int(unit.db_id), - qualification["qualification_id"], - int(worker.db_id), + app.db.update_worker_review( + unit_id=unit.db_id, + qualification_id=qualification["qualification_id"], + worker_id=worker.db_id, + value=value, + revoke=True, + ) + + @staticmethod + def _update_worker_qualification( + qualification: StringIDRow, + worker: Worker, + value: int, + explanation: Optional[str] = None, + ): + worker.grant_qualification(qualification["qualification_name"], value, skip_crowd=True) + + app.db.new_worker_review( + worker_id=worker.db_id, + qualification_id=qualification["qualification_id"], + value=value, + review_note=explanation, + status=UpdateGrantedQualificationStatus.GRANT, + revoke=False, + ) + + @staticmethod + def _revoke_worker_qualification( + qualification: StringIDRow, + worker: Worker, + explanation: Optional[str] = None, + ): + worker.revoke_qualification(qualification["qualification_name"], skip_crowd=True) + + app.db.new_worker_review( + worker_id=worker.db_id, + qualification_id=qualification["qualification_id"], + review_note=explanation, + status=UpdateGrantedQualificationStatus.REVOKE, + revoke=True, ) def post(self, qualification_id: int, worker_id: int, action: str) -> dict: - """Grant/Revoke qualification to a worker""" + """Grant/Revoke qualification to a worker with unit""" - data: dict = request.json - unit_ids: Optional[str] = data and data.get("unit_ids") - value = data and data.get("value") + data: dict = request.json or {} + unit_ids: Optional[List[str]] = data.get("unit_ids") + value: Optional[int] = data.get("value") if not unit_ids: raise BadRequest('Field "unit_ids" is required.') @@ -82,16 +143,66 @@ def post(self, qualification_id: int, worker_id: int, action: str) -> dict: # Do not raise any error, just ignore it return {} + worker: Worker = Worker.get(app.db, str(worker_id)) + for unit_id in unit_ids: unit: Unit = Unit.get(app.db, str(unit_id)) - worker: Worker = Worker.get(app.db, str(worker_id)) try: if action == "grant": - self._grant_worker_qualification(db_qualification, unit, worker, value or 1) + self.grant_worker_qualification_with_unit( + qualification=db_qualification, + unit=unit, + worker=worker, + value=value or 1, + ) elif action == "revoke": - self._revoke_worker_qualification(db_qualification, unit, worker) + self._revoke_worker_qualification_with_unit( + qualification=db_qualification, + unit=unit, + worker=worker, + value=value, + ) except Exception as e: raise BadRequest(f"Could not {action} qualification. Reason: {e}") return {} + + def patch(self, qualification_id: int, worker_id: int, action: str) -> dict: + """Update value of existing granted qualification or revoke qualification from a worker""" + + # TODO: Note that it will not affect `worker_review` table + # as we have required field `unit_id`, + # but in this case we update granted qualification directly + + data: dict = request.json or {} + value: Optional[int] = data.get("value") + explanation: Optional[str] = data.get("explanation") + + db_qualification: StringIDRow = app.db.get_qualification(qualification_id) + + if not db_qualification: + app.logger.debug(f"Could not found qualification with ID={qualification_id}") + # Do not raise any error, just ignore it + return {} + + worker: Worker = Worker.get(app.db, str(worker_id)) + + if action == "grant": + if not value: + raise BadRequest('Field "value" is required.') + + self._update_worker_qualification( + qualification=db_qualification, + worker=worker, + value=value, + explanation=explanation, + ) + elif action == "revoke": + self._revoke_worker_qualification( + qualification=db_qualification, + worker=worker, + explanation=explanation, + ) + + return {} diff --git a/mephisto/review_app/server/api/views/review_stats_view.py b/mephisto/review_app/server/api/views/review_stats_view.py index 9914c8177..cafbbcf42 100644 --- a/mephisto/review_app/server/api/views/review_stats_view.py +++ b/mephisto/review_app/server/api/views/review_stats_view.py @@ -20,7 +20,7 @@ from mephisto.data_model.constants.assignment_state import AssignmentState -def _find_unit_reviews( +def _find_worker_reviews( db, worker_id: Optional[str] = None, task_id: Optional[str] = None, @@ -71,9 +71,10 @@ def _find_unit_reviews( c = conn.cursor() c.execute( f""" - SELECT * FROM unit_review + SELECT * FROM worker_review {where_query} - ORDER BY creation_date ASC {limit_query}; + ORDER BY creation_date ASC + {limit_query}; """, params, ) @@ -159,7 +160,7 @@ def get(self) -> dict: except ParserError: raise BadRequest("Wrong date format.") - approved_unit_reviews = _find_unit_reviews( + approved_worker_reviews = _find_worker_reviews( db=app.db, worker_id=worker_id, task_id=task_id, @@ -167,7 +168,7 @@ def get(self) -> dict: since=since, limit=limit, ) - rejected_unit_reviews = _find_unit_reviews( + rejected_worker_reviews = _find_worker_reviews( db=app.db, worker_id=worker_id, task_id=task_id, @@ -175,7 +176,7 @@ def get(self) -> dict: since=since, limit=limit, ) - soft_rejected_unit_reviews = _find_unit_reviews( + soft_rejected_worker_reviews = _find_worker_reviews( db=app.db, worker_id=worker_id, task_id=task_id, @@ -193,15 +194,15 @@ def get(self) -> dict: ) reviewed_reviews = ( - approved_unit_reviews + rejected_unit_reviews + soft_rejected_unit_reviews + approved_worker_reviews + rejected_worker_reviews + soft_rejected_worker_reviews ) return { "stats": { "total_count": len(all_units_for_worker), # within the scope of the filters "reviewed_count": len(reviewed_reviews), - "approved_count": len(approved_unit_reviews), - "rejected_count": len(rejected_unit_reviews), - "soft_rejected_count": len(soft_rejected_unit_reviews), + "approved_count": len(approved_worker_reviews), + "rejected_count": len(rejected_worker_reviews), + "soft_rejected_count": len(soft_rejected_worker_reviews), }, } diff --git a/mephisto/review_app/server/api/views/task_view.py b/mephisto/review_app/server/api/views/task_view.py index 8fec0cfcf..65e797367 100644 --- a/mephisto/review_app/server/api/views/task_view.py +++ b/mephisto/review_app/server/api/views/task_view.py @@ -12,7 +12,7 @@ class TaskView(MethodView): def get(self, task_id: str = None) -> dict: - """Get all available tasks (to select one for review)""" + """Get task""" db_task: StringIDRow = app.db.get_task(task_id) app.logger.debug(f"Found Task in DB: {db_task}") diff --git a/mephisto/review_app/server/api/views/units_details_view.py b/mephisto/review_app/server/api/views/units_details_view.py index 58c7a3a2c..9b5033f14 100644 --- a/mephisto/review_app/server/api/views/units_details_view.py +++ b/mephisto/review_app/server/api/views/units_details_view.py @@ -11,6 +11,7 @@ from flask.views import MethodView from werkzeug.exceptions import BadRequest +from mephisto.abstractions.databases.local_database import LocalMephistoDB from mephisto.client.cli_form_composer_commands import set_form_composer_env_vars from mephisto.data_model.task_run import TaskRun from mephisto.data_model.unit import Unit @@ -26,6 +27,67 @@ from mephisto.review_app.server.utils.video_annotator import convert_annotation_tracks_to_webvtt +def _find_worker_reviews( + db: LocalMephistoDB, + unit_id: str, +) -> List[dict]: + """Return all unit reviews for unit""" + + with db.table_access_condition: + conn = db.get_connection() + c = conn.cursor() + c.execute( + f""" + SELECT + blocked_worker, + bonus, + creation_date, + qualification_name, + review_note, + revoked_qualification_id, + status, + updated_qualification_id, + updated_qualification_value + FROM worker_review AS wr + LEFT JOIN ( + SELECT + qualification_id, + qualification_name + FROM qualifications + ) AS q ON ( + ( + wr.updated_qualification_id = q.qualification_id AND + wr.revoked_qualification_id IS NULL + ) + OR + ( + wr.revoked_qualification_id = q.qualification_id AND + wr.updated_qualification_id IS NULL + ) + ) + WHERE unit_id = ?1 + ORDER BY creation_date DESC; + """, + [unit_id], + ) + rows = c.fetchall() + + worker_reviews = [ + { + "blocked_worker": r["blocked_worker"], + "bonus": r["bonus"], + "creation_date": r["creation_date"], + "qualification_id": r["updated_qualification_id"] or ["revoked_qualification_id"], + "qualification_name": r["qualification_name"], + "review_note": r["review_note"], + "status": r["status"], + "value": r["updated_qualification_value"], + } + for r in rows + ] + return worker_reviews + + class UnitsDetailsView(MethodView): def get(self) -> dict: """Get full input for specified workers results (`unit_ids` is mandatory)""" @@ -92,6 +154,8 @@ def get(self) -> dict: task_name = task_run.get_task().task_name metadata["webvtt"] = convert_annotation_tracks_to_webvtt(task_name, inputs, outputs) + metadata["worker_reviews"] = _find_worker_reviews(app.db, unit.db_id) + # Get Unit data path agent = unit.get_assigned_agent() unit_data_folder = agent.get_data_dir() if agent else None diff --git a/mephisto/review_app/server/api/views/worker_block_view.py b/mephisto/review_app/server/api/views/worker_block_view.py index a1d49e4d4..91a67132d 100644 --- a/mephisto/review_app/server/api/views/worker_block_view.py +++ b/mephisto/review_app/server/api/views/worker_block_view.py @@ -16,13 +16,13 @@ from mephisto.utils.db import EntryDoesNotExistException -def _update_blocked_worker_in_unit_review( +def _update_blocked_worker_in_worker_review( db, unit_id: int, worker_id: int, block: bool = False, ) -> None: - """Update unit review in the db with blocking Worker value""" + """Update worker review in the db with blocking Worker value""" with db.table_access_condition: conn = db.get_connection() @@ -30,7 +30,7 @@ def _update_blocked_worker_in_unit_review( c.execute( """ - SELECT * FROM unit_review + SELECT * FROM worker_review WHERE (unit_id = ?) AND (worker_id = ?) ORDER BY creation_date ASC; """, @@ -39,21 +39,21 @@ def _update_blocked_worker_in_unit_review( results = c.fetchall() if not results: raise EntryDoesNotExistException( - f"`unit_review` was not created for this `unit_id={unit_id}`" + f"`worker_review` was not created for this `unit_id={unit_id}`" ) - latest_unit_review_id = results[-1]["id"] + latest_worker_review_id = results[-1]["id"] c.execute( """ - UPDATE unit_review + UPDATE worker_review SET blocked_worker = ? WHERE id = ?; """, ( block, - latest_unit_review_id, + latest_worker_review_id, ), ) conn.commit() @@ -63,9 +63,9 @@ class WorkerBlockView(MethodView): def post(self, worker_id: int) -> dict: """Permanently block a worker""" - data: dict = request.json - unit_ids: Optional[str] = data and data.get("unit_ids") - review_note = data and data.get("review_note") + data: dict = request.json or {} + unit_ids: Optional[str] = data.get("unit_ids") + review_note = data.get("review_note") # Validate params if not review_note: @@ -79,6 +79,6 @@ def post(self, worker_id: int) -> dict: if unit_ids: for unit_id in unit_ids: unit: Unit = Unit.get(app.db, str(unit_id)) - _update_blocked_worker_in_unit_review(app.db, int(unit.db_id), worker_id, True) + _update_blocked_worker_in_worker_review(app.db, int(unit.db_id), worker_id, True) return {} diff --git a/mephisto/review_app/server/api/views/worker_granted_qualifications_view.py b/mephisto/review_app/server/api/views/worker_granted_qualifications_view.py index d756f2a2b..cda098abb 100644 --- a/mephisto/review_app/server/api/views/worker_granted_qualifications_view.py +++ b/mephisto/review_app/server/api/views/worker_granted_qualifications_view.py @@ -27,20 +27,20 @@ def _find_granted_qualifications(db: LocalMephistoDB, worker_id: str) -> List[St gq.worker_id, gq.qualification_id, gq.granted_qualification_id, - ur.creation_date AS granted_at + wr.creation_date AS granted_at FROM granted_qualifications AS gq LEFT JOIN ( SELECT updated_qualification_id, creation_date - FROM unit_review + FROM worker_review ORDER BY creation_date DESC /* - We’re retrieving unit_review data only + We’re retrieving `worker_review` data only for the latest update of the worker-qualification pair. */ LIMIT 1 - ) AS ur ON ur.updated_qualification_id = gq.qualification_id + ) AS wr ON wr.updated_qualification_id = gq.qualification_id WHERE gq.worker_id = ?1 """, (worker_id,), @@ -70,7 +70,7 @@ def get(self, worker_id: int) -> dict: "worker_id": gq["worker_id"], "qualification_id": gq["qualification_id"], "value": int(gq["value"]), - "granted_at": gq["granted_at"], # maps to `unit_review.creation_date` column + "granted_at": gq["granted_at"], # maps to `worker_review.creation_date` column }, ) diff --git a/mephisto/review_app/server/api/views/worker_qualifications_grant_view.py b/mephisto/review_app/server/api/views/worker_qualifications_grant_view.py new file mode 100644 index 000000000..fd2a4f44b --- /dev/null +++ b/mephisto/review_app/server/api/views/worker_qualifications_grant_view.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +# Copyright (c) Meta Platforms and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List + +from flask import current_app as app +from flask import request +from flask.views import MethodView +from werkzeug.exceptions import BadRequest + +from mephisto.abstractions.databases.local_database import StringIDRow +from mephisto.data_model.unit import Unit +from mephisto.data_model.worker import Worker +from mephisto.review_app.server.api.views import QualifyWorkerView + + +class WorkerQualificationsGrantView(MethodView): + def post(self, worker_id: int) -> dict: + """Grant multiple qualifications to a worker with units""" + + data: dict = request.json or {} + unit_ids: List[str] = data.get("unit_ids") + qualification_grants: List[dict] = data.get("qualification_grants", []) + + if not unit_ids: + raise BadRequest('Field "unit_ids" is required.') + + if not qualification_grants: + raise BadRequest('Field "qualification_grants" is required.') + + for qualification_grant in qualification_grants: + qualification_id = qualification_grant.get("qualification_id") + qualification_value = qualification_grant.get("value", 1) + + db_qualification: StringIDRow = app.db.get_qualification(qualification_id) + + if not db_qualification: + app.logger.debug(f"Could not found qualification with ID={qualification_id}") + # Do not raise any error, just ignore it + continue + + worker: Worker = Worker.get(app.db, str(worker_id)) + + for unit_id in unit_ids: + unit: Unit = Unit.get(app.db, str(unit_id)) + + QualifyWorkerView.grant_worker_qualification_with_unit( + qualification=db_qualification, + unit=unit, + worker=worker, + value=qualification_value, + ) + + return {} diff --git a/mephisto/review_app/server/urls.py b/mephisto/review_app/server/urls.py index 671ff40d2..d226d9202 100644 --- a/mephisto/review_app/server/urls.py +++ b/mephisto/review_app/server/urls.py @@ -34,6 +34,18 @@ def init_urls(app: Flask): "/api/qualifications", view_func=api_views.QualificationsView.as_view("qualifications"), ) + app.add_url_rule( + "/api/qualifications/", + view_func=api_views.QualificationView.as_view("qualification"), + ) + app.add_url_rule( + "/api/qualifications//details", + view_func=api_views.QualificationDetailsView.as_view("qualification_details"), + ) + app.add_url_rule( + "/api/granted-qualifications", + view_func=api_views.GrantedQualificationsView.as_view("granted_qualifications"), + ) app.add_url_rule( "/api/tasks//worker-units-ids", view_func=api_views.TaskUnitIdsView.as_view("worker_units_ids"), @@ -104,6 +116,10 @@ def init_urls(app: Flask): "worker_granted_qualifications", ), ) + app.add_url_rule( + "/api/workers//qualifications/grant", + view_func=api_views.WorkerQualificationsGrantView.as_view("worker_qualifications_grant"), + ) app.add_url_rule( "/api/review-stats", view_func=api_views.ReviewStatsView.as_view("review-stats"), diff --git a/mephisto/tools/db_data_porter/constants.py b/mephisto/tools/db_data_porter/constants.py index 93cafd80f..979ce4e16 100644 --- a/mephisto/tools/db_data_porter/constants.py +++ b/mephisto/tools/db_data_porter/constants.py @@ -179,7 +179,7 @@ AGENTS_TABLE_NAME: None, "onboarding_agents": None, "granted_qualifications": ["worker_id", "qualification_id"], - "unit_review": None, + "worker_review": None, }, PROLIFIC_PROVIDER_TYPE: { "workers": ["worker_id"], diff --git a/mephisto/tools/db_data_porter/dumps.py b/mephisto/tools/db_data_porter/dumps.py index 71df0f986..a2cf5d010 100644 --- a/mephisto/tools/db_data_porter/dumps.py +++ b/mephisto/tools/db_data_porter/dumps.py @@ -309,7 +309,7 @@ def delete_exported_data( "agents", "assignments", "task_runs", - "unit_review", + "worker_review", "units", ] if delete_tasks: diff --git a/mephisto/utils/db.py b/mephisto/utils/db.py index 275933883..3e3733cde 100644 --- a/mephisto/utils/db.py +++ b/mephisto/utils/db.py @@ -459,7 +459,7 @@ def mephisto_db_to_dict_for_task_runs( tables_with_task_relations = [ "tasks", - "unit_review", + "worker_review", ] # Find and serialize tables with `task_run_id` field diff --git a/mephisto/utils/testing.py b/mephisto/utils/testing.py index dbb1ac776..ccd2f4da3 100644 --- a/mephisto/utils/testing.py +++ b/mephisto/utils/testing.py @@ -224,7 +224,7 @@ def grant_test_qualification(db: MephistoDB, qualification_id: str, worker_id: s return db.grant_qualification(qualification_id, worker_id, value) -def find_unit_reviews( +def find_worker_reviews( db, qualification_id: str, worker_id: str, @@ -244,7 +244,7 @@ def find_unit_reviews( c = conn.cursor() c.execute( f""" - SELECT * FROM unit_review + SELECT * FROM worker_review WHERE (updated_qualification_id = ?1) OR (revoked_qualification_id = ?1) AND diff --git a/packages/mephisto-task-addons/build/bundle.js b/packages/mephisto-task-addons/build/bundle.js new file mode 100644 index 000000000..da4cc31cc --- /dev/null +++ b/packages/mephisto-task-addons/build/bundle.js @@ -0,0 +1,2 @@ +/*! For license information please see bundle.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports["mephisto-task-addons"]=t(require("react")):e["mephisto-task-addons"]=t(e.react)}(self,(__WEBPACK_EXTERNAL_MODULE__9155__=>(()=>{var __webpack_modules__={7495:(e,t,n)=>{"use strict";var r=n(9840);e.exports=function(e,t){return void 0===t&&(t=!1),function(n,i,o){if(n)e(n);else if(i.statusCode>=400&&i.statusCode<=599){var a=o;if(t)if(r.TextDecoder){var s=(void 0===(l=i.headers&&i.headers["content-type"])&&(l=""),l.toLowerCase().split(";").reduce((function(e,t){var n=t.split("="),r=n[0],i=n[1];return"charset"===r.trim()?i.trim():e}),"utf-8"));try{a=new TextDecoder(s).decode(o)}catch(e){}}else a=String.fromCharCode.apply(null,new Uint8Array(o));e({cause:a})}else e(null,o);var l}}},1036:(e,t,n)=>{"use strict";var r=n(9840),i=n(4634),o=n(7056),a=n(6162),s=n(8670);u.httpHandler=n(7495),u.requestInterceptorsStorage=new a,u.responseInterceptorsStorage=new a,u.retryManager=new s;var l=function(e){var t={};return e?(e.trim().split("\n").forEach((function(e){var n=e.indexOf(":"),r=e.slice(0,n).trim().toLowerCase(),i=e.slice(n+1).trim();void 0===t[r]?t[r]=i:Array.isArray(t[r])?t[r].push(i):t[r]=[t[r],i]})),t):t};function c(e,t,n){var r=e;return o(t)?(n=t,"string"==typeof e&&(r={uri:e})):r=i({},t,{uri:e}),r.callback=n,r}function u(e,t,n){return d(t=c(e,t,n))}function d(e){if(void 0===e.callback)throw new Error("callback argument missing");if(e.requestType&&u.requestInterceptorsStorage.getIsEnabled()){var t={uri:e.uri||e.url,headers:e.headers||{},body:e.body,metadata:e.metadata||{},retry:e.retry,timeout:e.timeout},n=u.requestInterceptorsStorage.execute(e.requestType,t);e.uri=n.uri,e.headers=n.headers,e.body=n.body,e.metadata=n.metadata,e.retry=n.retry,e.timeout=n.timeout}var r=!1,i=function(t,n,i){r||(r=!0,e.callback(t,n,i))};function o(){var e=void 0;if(e=h.response?h.response:h.responseText||function(e){try{if("document"===e.responseType)return e.responseXML;var t=e.responseXML&&"parsererror"===e.responseXML.documentElement.nodeName;if(""===e.responseType&&!t)return e.responseXML}catch(e){}return null}(h),_)try{e=JSON.parse(e)}catch(e){}return e}function a(t){if(clearTimeout(f),clearTimeout(e.retryTimeout),t instanceof Error||(t=new Error(""+(t||"Unknown XMLHttpRequest Error"))),t.statusCode=0,p||!u.retryManager.getIsEnabled()||!e.retry||!e.retry.shouldRetry()){if(e.requestType&&u.responseInterceptorsStorage.getIsEnabled()){var n={headers:w.headers||{},body:w.body,responseUrl:h.responseURL,responseType:h.responseType},r=u.responseInterceptorsStorage.execute(e.requestType,n);w.body=r.body,w.headers=r.headers}return i(t,w)}e.retryTimeout=setTimeout((function(){e.retry.moveToNextAttempt(),e.xhr=h,d(e)}),e.retry.getCurrentFuzzedDelay())}function s(){if(!p){var t;clearTimeout(f),clearTimeout(e.retryTimeout),t=e.useXDR&&void 0===h.status?200:1223===h.status?204:h.status;var n=w,r=null;if(0!==t?(n={body:o(),statusCode:t,method:g,headers:{},url:m,rawRequest:h},h.getAllResponseHeaders&&(n.headers=l(h.getAllResponseHeaders()))):r=new Error("Internal XMLHttpRequest Error"),e.requestType&&u.responseInterceptorsStorage.getIsEnabled()){var a={headers:n.headers||{},body:n.body,responseUrl:h.responseURL,responseType:h.responseType},s=u.responseInterceptorsStorage.execute(e.requestType,a);n.body=s.body,n.headers=s.headers}return i(r,n,n.body)}}var c,p,h=e.xhr||null;h||(h=e.cors||e.useXDR?new u.XDomainRequest:new u.XMLHttpRequest);var f,m=h.url=e.uri||e.url,g=h.method=e.method||"GET",v=e.body||e.data,b=h.headers=e.headers||{},y=!!e.sync,_=!1,w={body:void 0,headers:{},statusCode:0,method:g,url:m,rawRequest:h};if("json"in e&&!1!==e.json&&(_=!0,b.accept||b.Accept||(b.Accept="application/json"),"GET"!==g&&"HEAD"!==g&&(b["content-type"]||b["Content-Type"]||(b["Content-Type"]="application/json"),v=JSON.stringify(!0===e.json?v:e.json))),h.onreadystatechange=function(){4!==h.readyState||u.responseInterceptorsStorage.getIsEnabled()||setTimeout(s,0)},h.onload=s,h.onerror=a,h.onprogress=function(){},h.onabort=function(){p=!0,clearTimeout(e.retryTimeout)},h.ontimeout=a,h.open(g,m,!y,e.username,e.password),y||(h.withCredentials=!!e.withCredentials),!y&&e.timeout>0&&(f=setTimeout((function(){if(!p){p=!0,h.abort("timeout");var e=new Error("XMLHttpRequest timeout");e.code="ETIMEDOUT",a(e)}}),e.timeout)),h.setRequestHeader)for(c in b)b.hasOwnProperty(c)&&h.setRequestHeader(c,b[c]);else if(e.headers&&!function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0}(e.headers))throw new Error("Headers cannot be set on an XDomainRequest object");return"responseType"in e&&(h.responseType=e.responseType),"beforeSend"in e&&"function"==typeof e.beforeSend&&e.beforeSend(h),h.send(v||null),h}e.exports=u,e.exports.default=u,u.XMLHttpRequest=r.XMLHttpRequest||function(){},u.XDomainRequest="withCredentials"in new u.XMLHttpRequest?u.XMLHttpRequest:r.XDomainRequest,function(e,t){for(var n=0;n{"use strict";function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(this.getForType(e));!(r=i()).done;){var o=r.value;try{n=o(n)}catch(e){}}return n},e}();e.exports=n},8670:e=>{"use strict";var t=function(){function e(){this.maxAttempts_=1,this.delayFactor_=.1,this.fuzzFactor_=.1,this.initialDelay_=1e3,this.enabled_=!1}var t=e.prototype;return t.getIsEnabled=function(){return this.enabled_},t.enable=function(){this.enabled_=!0},t.disable=function(){this.enabled_=!1},t.reset=function(){this.maxAttempts_=1,this.delayFactor_=.1,this.fuzzFactor_=.1,this.initialDelay_=1e3,this.enabled_=!1},t.getMaxAttempts=function(){return this.maxAttempts_},t.setMaxAttempts=function(e){this.maxAttempts_=e},t.getDelayFactor=function(){return this.delayFactor_},t.setDelayFactor=function(e){this.delayFactor_=e},t.getFuzzFactor=function(){return this.fuzzFactor_},t.setFuzzFactor=function(e){this.fuzzFactor_=e},t.getInitialDelay=function(){return this.initialDelay_},t.setInitialDelay=function(e){this.initialDelay_=e},t.createRetry=function(e){var t=void 0===e?{}:e,r=t.maxAttempts,i=t.delayFactor,o=t.fuzzFactor,a=t.initialDelay;return new n({maxAttempts:r||this.maxAttempts_,delayFactor:i||this.delayFactor_,fuzzFactor:o||this.fuzzFactor_,initialDelay:a||this.initialDelay_})},e}(),n=function(){function e(e){this.maxAttempts_=e.maxAttempts,this.delayFactor_=e.delayFactor,this.fuzzFactor_=e.fuzzFactor,this.currentDelay_=e.initialDelay,this.currentAttempt_=1}var t=e.prototype;return t.moveToNextAttempt=function(){this.currentAttempt_++;var e=this.currentDelay_*this.delayFactor_;this.currentDelay_=this.currentDelay_+e},t.shouldRetry=function(){return this.currentAttempt_{"use strict";function n(e,t){return void 0===t&&(t=Object),t&&"function"==typeof t.freeze?t.freeze(e):e}var r=n({HTML:"text/html",isHTML:function(e){return e===r.HTML},XML_APPLICATION:"application/xml",XML_TEXT:"text/xml",XML_XHTML_APPLICATION:"application/xhtml+xml",XML_SVG_IMAGE:"image/svg+xml"}),i=n({HTML:"http://www.w3.org/1999/xhtml",isHTML:function(e){return e===i.HTML},SVG:"http://www.w3.org/2000/svg",XML:"http://www.w3.org/XML/1998/namespace",XMLNS:"http://www.w3.org/2000/xmlns/"});t.assign=function(e,t){if(null===e||"object"!=typeof e)throw new TypeError("target is not an object");for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},t.find=function(e,t,n){if(void 0===n&&(n=Array.prototype),e&&"function"==typeof n.find)return n.find.call(e,t);for(var r=0;r{var r=n(4582),i=n(4722),o=n(6559),a=n(4466),s=i.DOMImplementation,l=r.NAMESPACE,c=a.ParseError,u=a.XMLReader;function d(e){return e.replace(/\r[\n\u0085]/g,"\n").replace(/[\r\u0085\u2028]/g,"\n")}function p(e){this.options=e||{locator:{}}}function h(){this.cdata=!1}function f(e,t){t.lineNumber=e.lineNumber,t.columnNumber=e.columnNumber}function m(e){if(e)return"\n@"+(e.systemId||"")+"#[line:"+e.lineNumber+",col:"+e.columnNumber+"]"}function g(e,t,n){return"string"==typeof e?e.substr(t,n):e.length>=t+n||t?new java.lang.String(e,t,n)+"":e}function v(e,t){e.currentElement?e.currentElement.appendChild(t):e.doc.appendChild(t)}p.prototype.parseFromString=function(e,t){var n=this.options,r=new u,i=n.domBuilder||new h,a=n.errorHandler,s=n.locator,c=n.xmlns||{},p=/\/x?html?$/.test(t),f=p?o.HTML_ENTITIES:o.XML_ENTITIES;s&&i.setDocumentLocator(s),r.errorHandler=function(e,t,n){if(!e){if(t instanceof h)return t;e=t}var r={},i=e instanceof Function;function o(t){var o=e[t];!o&&i&&(o=2==e.length?function(n){e(t,n)}:e),r[t]=o&&function(e){o("[xmldom "+t+"]\t"+e+m(n))}||function(){}}return n=n||{},o("warning"),o("error"),o("fatalError"),r}(a,i,s),r.domBuilder=n.domBuilder||i,p&&(c[""]=l.HTML),c.xml=c.xml||l.XML;var g=n.normalizeLineEndings||d;return e&&"string"==typeof e?r.parse(g(e),c,f):r.errorHandler.error("invalid doc source"),i.doc},h.prototype={startDocument:function(){this.doc=(new s).createDocument(null,null,null),this.locator&&(this.doc.documentURI=this.locator.systemId)},startElement:function(e,t,n,r){var i=this.doc,o=i.createElementNS(e,n||t),a=r.length;v(this,o),this.currentElement=o,this.locator&&f(this.locator,o);for(var s=0;s{var r=n(4582),i=r.find,o=r.NAMESPACE;function a(e){return""!==e}function s(e,t){return e.hasOwnProperty(t)||(e[t]=!0),e}function l(e){if(!e)return[];var t=function(e){return e?e.split(/[\t\n\f\r ]+/).filter(a):[]}(e);return Object.keys(t.reduce(s,{}))}function c(e,t){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])}function u(e,t){var n=e.prototype;if(!(n instanceof t)){function r(){}r.prototype=t.prototype,c(n,r=new r),e.prototype=n=r}n.constructor!=e&&("function"!=typeof e&&console.error("unknown Class:"+e),n.constructor=e)}var d={},p=d.ELEMENT_NODE=1,h=d.ATTRIBUTE_NODE=2,f=d.TEXT_NODE=3,m=d.CDATA_SECTION_NODE=4,g=d.ENTITY_REFERENCE_NODE=5,v=d.ENTITY_NODE=6,b=d.PROCESSING_INSTRUCTION_NODE=7,y=d.COMMENT_NODE=8,_=d.DOCUMENT_NODE=9,w=d.DOCUMENT_TYPE_NODE=10,x=d.DOCUMENT_FRAGMENT_NODE=11,T=d.NOTATION_NODE=12,k={},S={},E=(k.INDEX_SIZE_ERR=(S[1]="Index size error",1),k.DOMSTRING_SIZE_ERR=(S[2]="DOMString size error",2),k.HIERARCHY_REQUEST_ERR=(S[3]="Hierarchy request error",3)),C=(k.WRONG_DOCUMENT_ERR=(S[4]="Wrong document",4),k.INVALID_CHARACTER_ERR=(S[5]="Invalid character",5),k.NO_DATA_ALLOWED_ERR=(S[6]="No data allowed",6),k.NO_MODIFICATION_ALLOWED_ERR=(S[7]="No modification allowed",7),k.NOT_FOUND_ERR=(S[8]="Not found",8)),A=(k.NOT_SUPPORTED_ERR=(S[9]="Not supported",9),k.INUSE_ATTRIBUTE_ERR=(S[10]="Attribute in use",10));function I(e,t){if(t instanceof Error)var n=t;else n=this,Error.call(this,S[e]),this.message=S[e],Error.captureStackTrace&&Error.captureStackTrace(this,I);return n.code=e,t&&(this.message=this.message+": "+t),n}function j(){}function O(e,t){this._node=e,this._refresh=t,P(this)}function P(e){var t=e._node._inc||e._node.ownerDocument._inc;if(e._inc!==t){var n=e._refresh(e._node);if(be(e,"length",n.length),!e.$$length||n.length=0))throw new I(C,new Error(e.tagName+"@"+n));for(var i=t.length-1;r"==e&&">")||"&"==e&&"&"||'"'==e&&"""||"&#"+e.charCodeAt()+";"}function B(e,t){if(t(e))return!0;if(e=e.firstChild)do{if(B(e,t))return!0}while(e=e.nextSibling)}function z(){this.ownerDocument=this}function q(e,t,n,r){e&&e._inc++,n.namespaceURI===o.XMLNS&&delete t._nsMap[n.prefix?n.localName:""]}function H(e,t,n){if(e&&e._inc){e._inc++;var r=t.childNodes;if(n)r[r.length++]=n;else{for(var i=t.firstChild,o=0;i;)r[o++]=i,i=i.nextSibling;r.length=o,delete r[r.length]}}}function $(e,t){var n=t.previousSibling,r=t.nextSibling;return n?n.nextSibling=r:e.firstChild=r,r?r.previousSibling=n:e.lastChild=n,t.parentNode=null,t.previousSibling=null,t.nextSibling=null,H(e.ownerDocument,e),t}function V(e){return e&&e.nodeType===U.DOCUMENT_TYPE_NODE}function W(e){return e&&e.nodeType===U.ELEMENT_NODE}function G(e){return e&&e.nodeType===U.TEXT_NODE}function X(e,t){var n=e.childNodes||[];if(i(n,W)||V(t))return!1;var r=i(n,V);return!(t&&r&&n.indexOf(r)>n.indexOf(t))}function K(e,t){var n=e.childNodes||[];if(i(n,(function(e){return W(e)&&e!==t})))return!1;var r=i(n,V);return!(t&&r&&n.indexOf(r)>n.indexOf(t))}function Y(e,t,n){var r=e.childNodes||[],o=t.childNodes||[];if(t.nodeType===U.DOCUMENT_FRAGMENT_NODE){var a=o.filter(W);if(a.length>1||i(o,G))throw new I(E,"More than one element or text in fragment");if(1===a.length&&!X(e,n))throw new I(E,"Element in fragment can not be inserted before doctype")}if(W(t)&&!X(e,n))throw new I(E,"Only one element can be added and only after doctype");if(V(t)){if(i(r,V))throw new I(E,"Only one doctype is allowed");var s=i(r,W);if(n&&r.indexOf(s)1||i(o,G))throw new I(E,"More than one element or text in fragment");if(1===a.length&&!K(e,n))throw new I(E,"Element in fragment can not be inserted before doctype")}if(W(t)&&!K(e,n))throw new I(E,"Only one element can be added and only after doctype");if(V(t)){if(i(r,(function(e){return V(e)&&e!==n})))throw new I(E,"Only one doctype is allowed");var s=i(r,W);if(n&&r.indexOf(s)=0;T--)if(""===(k=i[T]).prefix&&k.namespace===e.namespaceURI){d=k.namespace;break}if(d!==e.namespaceURI)for(T=i.length-1;T>=0;T--){var k;if((k=i[T]).namespace===e.namespaceURI){k.prefix&&(u=k.prefix+":"+c);break}}}t.push("<",u);for(var S=0;S"),n&&/^script$/i.test(c))for(;l;)l.data?t.push(l.data):me(l,t,n,r,i.slice()),l=l.nextSibling;else for(;l;)me(l,t,n,r,i.slice()),l=l.nextSibling;t.push("")}else t.push("/>");return;case _:case x:for(l=e.firstChild;l;)me(l,t,n,r,i.slice()),l=l.nextSibling;return;case h:return fe(t,e.name,e.value);case f:return t.push(e.data.replace(/[<&>]/g,F));case m:return t.push("");case y:return t.push("\x3c!--",e.data,"--\x3e");case w:var I=e.publicId,j=e.systemId;if(t.push("");else if(j&&"."!=j)t.push(" SYSTEM ",j,">");else{var O=e.internalSubset;O&&t.push(" [",O,"]"),t.push(">")}return;case b:return t.push("");case g:return t.push("&",e.nodeName,";");default:t.push("??",e.nodeName)}}function ge(e,t,n){var r;switch(t.nodeType){case p:(r=t.cloneNode(!1)).ownerDocument=e;case x:break;case h:n=!0}if(r||(r=t.cloneNode(!1)),r.ownerDocument=e,r.parentNode=null,n)for(var i=t.firstChild;i;)r.appendChild(ge(e,i,n)),i=i.nextSibling;return r}function ve(e,t,n){var r=new t.constructor;for(var i in t)if(Object.prototype.hasOwnProperty.call(t,i)){var o=t[i];"object"!=typeof o&&o!=r[i]&&(r[i]=o)}switch(t.childNodes&&(r.childNodes=new j),r.ownerDocument=e,r.nodeType){case p:var a=t.attributes,s=r.attributes=new D,l=a.length;s._ownerElement=r;for(var c=0;c=0&&e0},lookupPrefix:function(e){for(var t=this;t;){var n=t._nsMap;if(n)for(var r in n)if(Object.prototype.hasOwnProperty.call(n,r)&&n[r]===e)return r;t=t.nodeType==h?t.ownerDocument:t.parentNode}return null},lookupNamespaceURI:function(e){for(var t=this;t;){var n=t._nsMap;if(n&&Object.prototype.hasOwnProperty.call(n,e))return n[e];t=t.nodeType==h?t.ownerDocument:t.parentNode}return null},isDefaultNamespace:function(e){return null==this.lookupPrefix(e)}},c(d,U),c(d,U.prototype),z.prototype={nodeName:"#document",nodeType:_,doctype:null,documentElement:null,_inc:1,insertBefore:function(e,t){if(e.nodeType==x){for(var n=e.firstChild;n;){var r=n.nextSibling;this.insertBefore(n,t),n=r}return e}return J(this,e,t),e.ownerDocument=this,null===this.documentElement&&e.nodeType===p&&(this.documentElement=e),e},removeChild:function(e){return this.documentElement==e&&(this.documentElement=null),$(this,e)},replaceChild:function(e,t){J(this,e,t,Q),e.ownerDocument=this,t&&this.removeChild(t),W(e)&&(this.documentElement=e)},importNode:function(e,t){return ge(this,e,t)},getElementById:function(e){var t=null;return B(this.documentElement,(function(n){if(n.nodeType==p&&n.getAttribute("id")==e)return t=n,!0})),t},getElementsByClassName:function(e){var t=l(e);return new O(this,(function(n){var r=[];return t.length>0&&B(n.documentElement,(function(i){if(i!==n&&i.nodeType===p){var o=i.getAttribute("class");if(o){var a=e===o;if(!a){var s=l(o);a=t.every((c=s,function(e){return c&&-1!==c.indexOf(e)}))}a&&r.push(i)}}var c})),r}))},createElement:function(e){var t=new Z;return t.ownerDocument=this,t.nodeName=e,t.tagName=e,t.localName=e,t.childNodes=new j,(t.attributes=new D)._ownerElement=t,t},createDocumentFragment:function(){var e=new ce;return e.ownerDocument=this,e.childNodes=new j,e},createTextNode:function(e){var t=new ne;return t.ownerDocument=this,t.appendData(e),t},createComment:function(e){var t=new re;return t.ownerDocument=this,t.appendData(e),t},createCDATASection:function(e){var t=new ie;return t.ownerDocument=this,t.appendData(e),t},createProcessingInstruction:function(e,t){var n=new ue;return n.ownerDocument=this,n.tagName=n.nodeName=n.target=e,n.nodeValue=n.data=t,n},createAttribute:function(e){var t=new ee;return t.ownerDocument=this,t.name=e,t.nodeName=e,t.localName=e,t.specified=!0,t},createEntityReference:function(e){var t=new le;return t.ownerDocument=this,t.nodeName=e,t},createElementNS:function(e,t){var n=new Z,r=t.split(":"),i=n.attributes=new D;return n.childNodes=new j,n.ownerDocument=this,n.nodeName=t,n.tagName=t,n.namespaceURI=e,2==r.length?(n.prefix=r[0],n.localName=r[1]):n.localName=t,i._ownerElement=n,n},createAttributeNS:function(e,t){var n=new ee,r=t.split(":");return n.ownerDocument=this,n.nodeName=t,n.name=t,n.namespaceURI=e,n.specified=!0,2==r.length?(n.prefix=r[0],n.localName=r[1]):n.localName=t,n}},u(z,U),Z.prototype={nodeType:p,hasAttribute:function(e){return null!=this.getAttributeNode(e)},getAttribute:function(e){var t=this.getAttributeNode(e);return t&&t.value||""},getAttributeNode:function(e){return this.attributes.getNamedItem(e)},setAttribute:function(e,t){var n=this.ownerDocument.createAttribute(e);n.value=n.nodeValue=""+t,this.setAttributeNode(n)},removeAttribute:function(e){var t=this.getAttributeNode(e);t&&this.removeAttributeNode(t)},appendChild:function(e){return e.nodeType===x?this.insertBefore(e,null):function(e,t){return t.parentNode&&t.parentNode.removeChild(t),t.parentNode=e,t.previousSibling=e.lastChild,t.nextSibling=null,t.previousSibling?t.previousSibling.nextSibling=t:e.firstChild=t,e.lastChild=t,H(e.ownerDocument,e,t),t}(this,e)},setAttributeNode:function(e){return this.attributes.setNamedItem(e)},setAttributeNodeNS:function(e){return this.attributes.setNamedItemNS(e)},removeAttributeNode:function(e){return this.attributes.removeNamedItem(e.nodeName)},removeAttributeNS:function(e,t){var n=this.getAttributeNodeNS(e,t);n&&this.removeAttributeNode(n)},hasAttributeNS:function(e,t){return null!=this.getAttributeNodeNS(e,t)},getAttributeNS:function(e,t){var n=this.getAttributeNodeNS(e,t);return n&&n.value||""},setAttributeNS:function(e,t,n){var r=this.ownerDocument.createAttributeNS(e,t);r.value=r.nodeValue=""+n,this.setAttributeNode(r)},getAttributeNodeNS:function(e,t){return this.attributes.getNamedItemNS(e,t)},getElementsByTagName:function(e){return new O(this,(function(t){var n=[];return B(t,(function(r){r===t||r.nodeType!=p||"*"!==e&&r.tagName!=e||n.push(r)})),n}))},getElementsByTagNameNS:function(e,t){return new O(this,(function(n){var r=[];return B(n,(function(i){i===n||i.nodeType!==p||"*"!==e&&i.namespaceURI!==e||"*"!==t&&i.localName!=t||r.push(i)})),r}))}},z.prototype.getElementsByTagName=Z.prototype.getElementsByTagName,z.prototype.getElementsByTagNameNS=Z.prototype.getElementsByTagNameNS,u(Z,U),ee.prototype.nodeType=h,u(ee,U),te.prototype={data:"",substringData:function(e,t){return this.data.substring(e,e+t)},appendData:function(e){e=this.data+e,this.nodeValue=this.data=e,this.length=e.length},insertData:function(e,t){this.replaceData(e,0,t)},appendChild:function(e){throw new Error(S[E])},deleteData:function(e,t){this.replaceData(e,t,"")},replaceData:function(e,t,n){n=this.data.substring(0,e)+n+this.data.substring(e+t),this.nodeValue=this.data=n,this.length=n.length}},u(te,U),ne.prototype={nodeName:"#text",nodeType:f,splitText:function(e){var t=this.data,n=t.substring(e);t=t.substring(0,e),this.data=this.nodeValue=t,this.length=t.length;var r=this.ownerDocument.createTextNode(n);return this.parentNode&&this.parentNode.insertBefore(r,this.nextSibling),r}},u(ne,te),re.prototype={nodeName:"#comment",nodeType:y},u(re,te),ie.prototype={nodeName:"#cdata-section",nodeType:m},u(ie,te),oe.prototype.nodeType=w,u(oe,U),ae.prototype.nodeType=T,u(ae,U),se.prototype.nodeType=v,u(se,U),le.prototype.nodeType=g,u(le,U),ce.prototype.nodeName="#document-fragment",ce.prototype.nodeType=x,u(ce,U),ue.prototype.nodeType=b,u(ue,U),de.prototype.serializeToString=function(e,t,n){return pe.call(e,t,n)},U.prototype.toString=pe;try{if(Object.defineProperty){function ye(e){switch(e.nodeType){case p:case x:var t=[];for(e=e.firstChild;e;)7!==e.nodeType&&8!==e.nodeType&&t.push(ye(e)),e=e.nextSibling;return t.join("");default:return e.nodeValue}}Object.defineProperty(O.prototype,"length",{get:function(){return P(this),this.$$length}}),Object.defineProperty(U.prototype,"textContent",{get:function(){return ye(this)},set:function(e){switch(this.nodeType){case p:case x:for(;this.firstChild;)this.removeChild(this.firstChild);(e||String(e))&&this.appendChild(this.ownerDocument.createTextNode(e));break;default:this.data=e,this.value=e,this.nodeValue=e}}}),be=function(e,t,n){e["$$"+t]=n}}}catch(_e){}t.DocumentType=oe,t.DOMException=I,t.DOMImplementation=R,t.Element=Z,t.Node=U,t.NodeList=j,t.XMLSerializer=de},6559:(e,t,n)=>{"use strict";var r=n(4582).freeze;t.XML_ENTITIES=r({amp:"&",apos:"'",gt:">",lt:"<",quot:'"'}),t.HTML_ENTITIES=r({Aacute:"Á",aacute:"á",Abreve:"Ă",abreve:"ă",ac:"∾",acd:"∿",acE:"∾̳",Acirc:"Â",acirc:"â",acute:"´",Acy:"А",acy:"а",AElig:"Æ",aelig:"æ",af:"⁡",Afr:"𝔄",afr:"𝔞",Agrave:"À",agrave:"à",alefsym:"ℵ",aleph:"ℵ",Alpha:"Α",alpha:"α",Amacr:"Ā",amacr:"ā",amalg:"⨿",AMP:"&",amp:"&",And:"⩓",and:"∧",andand:"⩕",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsd:"∡",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",Aogon:"Ą",aogon:"ą",Aopf:"𝔸",aopf:"𝕒",ap:"≈",apacir:"⩯",apE:"⩰",ape:"≊",apid:"≋",apos:"'",ApplyFunction:"⁡",approx:"≈",approxeq:"≊",Aring:"Å",aring:"å",Ascr:"𝒜",ascr:"𝒶",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",Atilde:"Ã",atilde:"ã",Auml:"Ä",auml:"ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",Barwed:"⌆",barwed:"⌅",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",Bcy:"Б",bcy:"б",bdquo:"„",becaus:"∵",Because:"∵",because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",Beta:"Β",beta:"β",beth:"ℶ",between:"≬",Bfr:"𝔅",bfr:"𝔟",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bNot:"⫭",bnot:"⌐",Bopf:"𝔹",bopf:"𝕓",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxDL:"╗",boxDl:"╖",boxdL:"╕",boxdl:"┐",boxDR:"╔",boxDr:"╓",boxdR:"╒",boxdr:"┌",boxH:"═",boxh:"─",boxHD:"╦",boxHd:"╤",boxhD:"╥",boxhd:"┬",boxHU:"╩",boxHu:"╧",boxhU:"╨",boxhu:"┴",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxUL:"╝",boxUl:"╜",boxuL:"╛",boxul:"┘",boxUR:"╚",boxUr:"╙",boxuR:"╘",boxur:"└",boxV:"║",boxv:"│",boxVH:"╬",boxVh:"╫",boxvH:"╪",boxvh:"┼",boxVL:"╣",boxVl:"╢",boxvL:"╡",boxvl:"┤",boxVR:"╠",boxVr:"╟",boxvR:"╞",boxvr:"├",bprime:"‵",Breve:"˘",breve:"˘",brvbar:"¦",Bscr:"ℬ",bscr:"𝒷",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpE:"⪮",bumpe:"≏",Bumpeq:"≎",bumpeq:"≏",Cacute:"Ć",cacute:"ć",Cap:"⋒",cap:"∩",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",Ccaron:"Č",ccaron:"č",Ccedil:"Ç",ccedil:"ç",Ccirc:"Ĉ",ccirc:"ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",Cdot:"Ċ",cdot:"ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",CenterDot:"·",centerdot:"·",Cfr:"ℭ",cfr:"𝔠",CHcy:"Ч",chcy:"ч",check:"✓",checkmark:"✓",Chi:"Χ",chi:"χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cirE:"⧃",cire:"≗",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",Colon:"∷",colon:":",Colone:"⩴",colone:"≔",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",Congruent:"≡",Conint:"∯",conint:"∮",ContourIntegral:"∮",Copf:"ℂ",copf:"𝕔",coprod:"∐",Coproduct:"∐",COPY:"©",copy:"©",copysr:"℗",CounterClockwiseContourIntegral:"∳",crarr:"↵",Cross:"⨯",cross:"✗",Cscr:"𝒞",cscr:"𝒸",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",Cup:"⋓",cup:"∪",cupbrcap:"⩈",CupCap:"≍",cupcap:"⩆",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",Dagger:"‡",dagger:"†",daleth:"ℸ",Darr:"↡",dArr:"⇓",darr:"↓",dash:"‐",Dashv:"⫤",dashv:"⊣",dbkarow:"⤏",dblac:"˝",Dcaron:"Ď",dcaron:"ď",Dcy:"Д",dcy:"д",DD:"ⅅ",dd:"ⅆ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⩷",deg:"°",Del:"∇",Delta:"Δ",delta:"δ",demptyv:"⦱",dfisht:"⥿",Dfr:"𝔇",dfr:"𝔡",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",Diamond:"⋄",diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"ⅆ",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",DJcy:"Ђ",djcy:"ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",Dopf:"𝔻",dopf:"𝕕",Dot:"¨",dot:"˙",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",DownArrow:"↓",Downarrow:"⇓",downarrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"⇵",DownBreve:"̑",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"⥐",DownLeftTeeVector:"⥞",DownLeftVector:"↽",DownLeftVectorBar:"⥖",DownRightTeeVector:"⥟",DownRightVector:"⇁",DownRightVectorBar:"⥗",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",Dscr:"𝒟",dscr:"𝒹",DScy:"Ѕ",dscy:"ѕ",dsol:"⧶",Dstrok:"Đ",dstrok:"đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",DZcy:"Џ",dzcy:"џ",dzigrarr:"⟿",Eacute:"É",eacute:"é",easter:"⩮",Ecaron:"Ě",ecaron:"ě",ecir:"≖",Ecirc:"Ê",ecirc:"ê",ecolon:"≕",Ecy:"Э",ecy:"э",eDDot:"⩷",Edot:"Ė",eDot:"≑",edot:"ė",ee:"ⅇ",efDot:"≒",Efr:"𝔈",efr:"𝔢",eg:"⪚",Egrave:"È",egrave:"è",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",Emacr:"Ē",emacr:"ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"◻",emptyv:"∅",EmptyVerySmallSquare:"▫",emsp:" ",emsp13:" ",emsp14:" ",ENG:"Ŋ",eng:"ŋ",ensp:" ",Eogon:"Ę",eogon:"ę",Eopf:"𝔼",eopf:"𝕖",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",Epsilon:"Ε",epsilon:"ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",Escr:"ℰ",escr:"ℯ",esdot:"≐",Esim:"⩳",esim:"≂",Eta:"Η",eta:"η",ETH:"Ð",eth:"ð",Euml:"Ë",euml:"ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",ExponentialE:"ⅇ",exponentiale:"ⅇ",fallingdotseq:"≒",Fcy:"Ф",fcy:"ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",Ffr:"𝔉",ffr:"𝔣",filig:"fi",FilledSmallSquare:"◼",FilledVerySmallSquare:"▪",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",Fopf:"𝔽",fopf:"𝕗",ForAll:"∀",forall:"∀",fork:"⋔",forkv:"⫙",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",Fscr:"ℱ",fscr:"𝒻",gacute:"ǵ",Gamma:"Γ",gamma:"γ",Gammad:"Ϝ",gammad:"ϝ",gap:"⪆",Gbreve:"Ğ",gbreve:"ğ",Gcedil:"Ģ",Gcirc:"Ĝ",gcirc:"ĝ",Gcy:"Г",gcy:"г",Gdot:"Ġ",gdot:"ġ",gE:"≧",ge:"≥",gEl:"⪌",gel:"⋛",geq:"≥",geqq:"≧",geqslant:"⩾",ges:"⩾",gescc:"⪩",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",Gfr:"𝔊",gfr:"𝔤",Gg:"⋙",gg:"≫",ggg:"⋙",gimel:"ℷ",GJcy:"Ѓ",gjcy:"ѓ",gl:"≷",gla:"⪥",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gnE:"≩",gne:"⪈",gneq:"⪈",gneqq:"≩",gnsim:"⋧",Gopf:"𝔾",gopf:"𝕘",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",Gscr:"𝒢",gscr:"ℊ",gsim:"≳",gsime:"⪎",gsiml:"⪐",Gt:"≫",GT:">",gt:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",HARDcy:"Ъ",hardcy:"ъ",hArr:"⇔",harr:"↔",harrcir:"⥈",harrw:"↭",Hat:"^",hbar:"ℏ",Hcirc:"Ĥ",hcirc:"ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",Hfr:"ℌ",hfr:"𝔥",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",Hopf:"ℍ",hopf:"𝕙",horbar:"―",HorizontalLine:"─",Hscr:"ℋ",hscr:"𝒽",hslash:"ℏ",Hstrok:"Ħ",hstrok:"ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",Iacute:"Í",iacute:"í",ic:"⁣",Icirc:"Î",icirc:"î",Icy:"И",icy:"и",Idot:"İ",IEcy:"Е",iecy:"е",iexcl:"¡",iff:"⇔",Ifr:"ℑ",ifr:"𝔦",Igrave:"Ì",igrave:"ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",IJlig:"IJ",ijlig:"ij",Im:"ℑ",Imacr:"Ī",imacr:"ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",Int:"∬",int:"∫",intcal:"⊺",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"⁣",InvisibleTimes:"⁢",IOcy:"Ё",iocy:"ё",Iogon:"Į",iogon:"į",Iopf:"𝕀",iopf:"𝕚",Iota:"Ι",iota:"ι",iprod:"⨼",iquest:"¿",Iscr:"ℐ",iscr:"𝒾",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",Itilde:"Ĩ",itilde:"ĩ",Iukcy:"І",iukcy:"і",Iuml:"Ï",iuml:"ï",Jcirc:"Ĵ",jcirc:"ĵ",Jcy:"Й",jcy:"й",Jfr:"𝔍",jfr:"𝔧",jmath:"ȷ",Jopf:"𝕁",jopf:"𝕛",Jscr:"𝒥",jscr:"𝒿",Jsercy:"Ј",jsercy:"ј",Jukcy:"Є",jukcy:"є",Kappa:"Κ",kappa:"κ",kappav:"ϰ",Kcedil:"Ķ",kcedil:"ķ",Kcy:"К",kcy:"к",Kfr:"𝔎",kfr:"𝔨",kgreen:"ĸ",KHcy:"Х",khcy:"х",KJcy:"Ќ",kjcy:"ќ",Kopf:"𝕂",kopf:"𝕜",Kscr:"𝒦",kscr:"𝓀",lAarr:"⇚",Lacute:"Ĺ",lacute:"ĺ",laemptyv:"⦴",lagran:"ℒ",Lambda:"Λ",lambda:"λ",Lang:"⟪",lang:"⟨",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",Larr:"↞",lArr:"⇐",larr:"←",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",lAtail:"⤛",latail:"⤙",late:"⪭",lates:"⪭︀",lBarr:"⤎",lbarr:"⤌",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",Lcaron:"Ľ",lcaron:"ľ",Lcedil:"Ļ",lcedil:"ļ",lceil:"⌈",lcub:"{",Lcy:"Л",lcy:"л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",lE:"≦",le:"≤",LeftAngleBracket:"⟨",LeftArrow:"←",Leftarrow:"⇐",leftarrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",LeftRightArrow:"↔",Leftrightarrow:"⇔",leftrightarrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",lEg:"⪋",leg:"⋚",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",Lfr:"𝔏",lfr:"𝔩",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",LJcy:"Љ",ljcy:"љ",Ll:"⋘",ll:"≪",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",Lmidot:"Ŀ",lmidot:"ŀ",lmoust:"⎰",lmoustache:"⎰",lnap:"⪉",lnapprox:"⪉",lnE:"≨",lne:"⪇",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",LongLeftArrow:"⟵",Longleftarrow:"⟸",longleftarrow:"⟵",LongLeftRightArrow:"⟷",Longleftrightarrow:"⟺",longleftrightarrow:"⟷",longmapsto:"⟼",LongRightArrow:"⟶",Longrightarrow:"⟹",longrightarrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",Lopf:"𝕃",lopf:"𝕝",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",Lscr:"ℒ",lscr:"𝓁",Lsh:"↰",lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",Lstrok:"Ł",lstrok:"ł",Lt:"≪",LT:"<",lt:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",Map:"⤅",map:"↦",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",Mcy:"М",mcy:"м",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",Mfr:"𝔐",mfr:"𝔪",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",Mopf:"𝕄",mopf:"𝕞",mp:"∓",Mscr:"ℳ",mscr:"𝓂",mstpos:"∾",Mu:"Μ",mu:"μ",multimap:"⊸",mumap:"⊸",nabla:"∇",Nacute:"Ń",nacute:"ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",Ncaron:"Ň",ncaron:"ň",Ncedil:"Ņ",ncedil:"ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",Ncy:"Н",ncy:"н",ndash:"–",ne:"≠",nearhk:"⤤",neArr:"⇗",nearr:"↗",nearrow:"↗",nedot:"≐̸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",Nfr:"𝔑",nfr:"𝔫",ngE:"≧̸",nge:"≱",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",nGt:"≫⃒",ngt:"≯",ngtr:"≯",nGtv:"≫̸",nhArr:"⇎",nharr:"↮",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",NJcy:"Њ",njcy:"њ",nlArr:"⇍",nlarr:"↚",nldr:"‥",nlE:"≦̸",nle:"≰",nLeftarrow:"⇍",nleftarrow:"↚",nLeftrightarrow:"⇎",nleftrightarrow:"↮",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nLt:"≪⃒",nlt:"≮",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",Nopf:"ℕ",nopf:"𝕟",Not:"⫬",not:"¬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrArr:"⇏",nrarr:"↛",nrarrc:"⤳̸",nrarrw:"↝̸",nRightarrow:"⇏",nrightarrow:"↛",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",Nscr:"𝒩",nscr:"𝓃",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsubE:"⫅̸",nsube:"⊈",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupE:"⫆̸",nsupe:"⊉",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",Ntilde:"Ñ",ntilde:"ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",Nu:"Ν",nu:"ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nVDash:"⊯",nVdash:"⊮",nvDash:"⊭",nvdash:"⊬",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwArr:"⇖",nwarr:"↖",nwarrow:"↖",nwnear:"⤧",Oacute:"Ó",oacute:"ó",oast:"⊛",ocir:"⊚",Ocirc:"Ô",ocirc:"ô",Ocy:"О",ocy:"о",odash:"⊝",Odblac:"Ő",odblac:"ő",odiv:"⨸",odot:"⊙",odsold:"⦼",OElig:"Œ",oelig:"œ",ofcir:"⦿",Ofr:"𝔒",ofr:"𝔬",ogon:"˛",Ograve:"Ò",ograve:"ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",Omacr:"Ō",omacr:"ō",Omega:"Ω",omega:"ω",Omicron:"Ο",omicron:"ο",omid:"⦶",ominus:"⊖",Oopf:"𝕆",oopf:"𝕠",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",Or:"⩔",or:"∨",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",Oscr:"𝒪",oscr:"ℴ",Oslash:"Ø",oslash:"ø",osol:"⊘",Otilde:"Õ",otilde:"õ",Otimes:"⨷",otimes:"⊗",otimesas:"⨶",Ouml:"Ö",ouml:"ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",Pcy:"П",pcy:"п",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",Pfr:"𝔓",pfr:"𝔭",Phi:"Φ",phi:"φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",Pi:"Π",pi:"π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",Popf:"ℙ",popf:"𝕡",pound:"£",Pr:"⪻",pr:"≺",prap:"⪷",prcue:"≼",prE:"⪳",pre:"⪯",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",Prime:"″",prime:"′",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∷",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",Pscr:"𝒫",pscr:"𝓅",Psi:"Ψ",psi:"ψ",puncsp:" ",Qfr:"𝔔",qfr:"𝔮",qint:"⨌",Qopf:"ℚ",qopf:"𝕢",qprime:"⁗",Qscr:"𝒬",qscr:"𝓆",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",QUOT:'"',quot:'"',rAarr:"⇛",race:"∽̱",Racute:"Ŕ",racute:"ŕ",radic:"√",raemptyv:"⦳",Rang:"⟫",rang:"⟩",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",Rarr:"↠",rArr:"⇒",rarr:"→",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",Rarrtl:"⤖",rarrtl:"↣",rarrw:"↝",rAtail:"⤜",ratail:"⤚",ratio:"∶",rationals:"ℚ",RBarr:"⤐",rBarr:"⤏",rbarr:"⤍",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",Rcaron:"Ř",rcaron:"ř",Rcedil:"Ŗ",rcedil:"ŗ",rceil:"⌉",rcub:"}",Rcy:"Р",rcy:"р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",REG:"®",reg:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",Rfr:"ℜ",rfr:"𝔯",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",Rho:"Ρ",rho:"ρ",rhov:"ϱ",RightAngleBracket:"⟩",RightArrow:"→",Rightarrow:"⇒",rightarrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",Ropf:"ℝ",ropf:"𝕣",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",Rscr:"ℛ",rscr:"𝓇",Rsh:"↱",rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",Sacute:"Ś",sacute:"ś",sbquo:"‚",Sc:"⪼",sc:"≻",scap:"⪸",Scaron:"Š",scaron:"š",sccue:"≽",scE:"⪴",sce:"⪰",Scedil:"Ş",scedil:"ş",Scirc:"Ŝ",scirc:"ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",Scy:"С",scy:"с",sdot:"⋅",sdotb:"⊡",sdote:"⩦",searhk:"⤥",seArr:"⇘",searr:"↘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",Sfr:"𝔖",sfr:"𝔰",sfrown:"⌢",sharp:"♯",SHCHcy:"Щ",shchcy:"щ",SHcy:"Ш",shcy:"ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",Sigma:"Σ",sigma:"σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",SOFTcy:"Ь",softcy:"ь",sol:"/",solb:"⧄",solbar:"⌿",Sopf:"𝕊",sopf:"𝕤",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",Square:"□",square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squf:"▪",srarr:"→",Sscr:"𝒮",sscr:"𝓈",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",Star:"⋆",star:"☆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",Sub:"⋐",sub:"⊂",subdot:"⪽",subE:"⫅",sube:"⊆",subedot:"⫃",submult:"⫁",subnE:"⫋",subne:"⊊",subplus:"⪿",subrarr:"⥹",Subset:"⋐",subset:"⊂",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",Sum:"∑",sum:"∑",sung:"♪",Sup:"⋑",sup:"⊃",sup1:"¹",sup2:"²",sup3:"³",supdot:"⪾",supdsub:"⫘",supE:"⫆",supe:"⊇",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supnE:"⫌",supne:"⊋",supplus:"⫀",Supset:"⋑",supset:"⊃",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swArr:"⇙",swarr:"↙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",Tau:"Τ",tau:"τ",tbrk:"⎴",Tcaron:"Ť",tcaron:"ť",Tcedil:"Ţ",tcedil:"ţ",Tcy:"Т",tcy:"т",tdot:"⃛",telrec:"⌕",Tfr:"𝔗",tfr:"𝔱",there4:"∴",Therefore:"∴",therefore:"∴",Theta:"Θ",theta:"θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"∼",THORN:"Þ",thorn:"þ",Tilde:"∼",tilde:"˜",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",Topf:"𝕋",topf:"𝕥",topfork:"⫚",tosa:"⤩",tprime:"‴",TRADE:"™",trade:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",Tscr:"𝒯",tscr:"𝓉",TScy:"Ц",tscy:"ц",TSHcy:"Ћ",tshcy:"ћ",Tstrok:"Ŧ",tstrok:"ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",Uacute:"Ú",uacute:"ú",Uarr:"↟",uArr:"⇑",uarr:"↑",Uarrocir:"⥉",Ubrcy:"Ў",ubrcy:"ў",Ubreve:"Ŭ",ubreve:"ŭ",Ucirc:"Û",ucirc:"û",Ucy:"У",ucy:"у",udarr:"⇅",Udblac:"Ű",udblac:"ű",udhar:"⥮",ufisht:"⥾",Ufr:"𝔘",ufr:"𝔲",Ugrave:"Ù",ugrave:"ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",Umacr:"Ū",umacr:"ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",Uogon:"Ų",uogon:"ų",Uopf:"𝕌",uopf:"𝕦",UpArrow:"↑",Uparrow:"⇑",uparrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",UpDownArrow:"↕",Updownarrow:"⇕",updownarrow:"↕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",Upsi:"ϒ",upsi:"υ",upsih:"ϒ",Upsilon:"Υ",upsilon:"υ",UpTee:"⊥",UpTeeArrow:"↥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",Uring:"Ů",uring:"ů",urtri:"◹",Uscr:"𝒰",uscr:"𝓊",utdot:"⋰",Utilde:"Ũ",utilde:"ũ",utri:"▵",utrif:"▴",uuarr:"⇈",Uuml:"Ü",uuml:"ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",vArr:"⇕",varr:"↕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",Vbar:"⫫",vBar:"⫨",vBarv:"⫩",Vcy:"В",vcy:"в",VDash:"⊫",Vdash:"⊩",vDash:"⊨",vdash:"⊢",Vdashl:"⫦",Vee:"⋁",vee:"∨",veebar:"⊻",veeeq:"≚",vellip:"⋮",Verbar:"‖",verbar:"|",Vert:"‖",vert:"|",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",Vfr:"𝔙",vfr:"𝔳",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",Vopf:"𝕍",vopf:"𝕧",vprop:"∝",vrtri:"⊳",Vscr:"𝒱",vscr:"𝓋",vsubnE:"⫋︀",vsubne:"⊊︀",vsupnE:"⫌︀",vsupne:"⊋︀",Vvdash:"⊪",vzigzag:"⦚",Wcirc:"Ŵ",wcirc:"ŵ",wedbar:"⩟",Wedge:"⋀",wedge:"∧",wedgeq:"≙",weierp:"℘",Wfr:"𝔚",wfr:"𝔴",Wopf:"𝕎",wopf:"𝕨",wp:"℘",wr:"≀",wreath:"≀",Wscr:"𝒲",wscr:"𝓌",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",Xfr:"𝔛",xfr:"𝔵",xhArr:"⟺",xharr:"⟷",Xi:"Ξ",xi:"ξ",xlArr:"⟸",xlarr:"⟵",xmap:"⟼",xnis:"⋻",xodot:"⨀",Xopf:"𝕏",xopf:"𝕩",xoplus:"⨁",xotime:"⨂",xrArr:"⟹",xrarr:"⟶",Xscr:"𝒳",xscr:"𝓍",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",Yacute:"Ý",yacute:"ý",YAcy:"Я",yacy:"я",Ycirc:"Ŷ",ycirc:"ŷ",Ycy:"Ы",ycy:"ы",yen:"¥",Yfr:"𝔜",yfr:"𝔶",YIcy:"Ї",yicy:"ї",Yopf:"𝕐",yopf:"𝕪",Yscr:"𝒴",yscr:"𝓎",YUcy:"Ю",yucy:"ю",Yuml:"Ÿ",yuml:"ÿ",Zacute:"Ź",zacute:"ź",Zcaron:"Ž",zcaron:"ž",Zcy:"З",zcy:"з",Zdot:"Ż",zdot:"ż",zeetrf:"ℨ",ZeroWidthSpace:"​",Zeta:"Ζ",zeta:"ζ",Zfr:"ℨ",zfr:"𝔷",ZHcy:"Ж",zhcy:"ж",zigrarr:"⇝",Zopf:"ℤ",zopf:"𝕫",Zscr:"𝒵",zscr:"𝓏",zwj:"‍",zwnj:"‌"}),t.entityMap=t.HTML_ENTITIES},8978:(e,t,n)=>{var r=n(4722);r.DOMImplementation,r.XMLSerializer,t.DOMParser=n(5752).DOMParser},4466:(e,t,n)=>{var r=n(4582).NAMESPACE,i=/[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/,o=new RegExp("[\\-\\.0-9"+i.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"),a=new RegExp("^"+i.source+o.source+"*(?::"+i.source+o.source+"*)?$");function s(e,t){this.message=e,this.locator=t,Error.captureStackTrace&&Error.captureStackTrace(this,s)}function l(){}function c(e,t){return t.lineNumber=e.lineNumber,t.columnNumber=e.columnNumber,t}function u(e,t,n,i,o,a){function s(e,t,r){n.attributeNames.hasOwnProperty(e)&&a.fatalError("Attribute "+e+" redefined"),n.addValue(e,t.replace(/[\t\n\r]/g," ").replace(/&#?\w+;/g,o),r)}for(var l,c=++t,u=0;;){var d=e.charAt(c);switch(d){case"=":if(1===u)l=e.slice(t,c),u=3;else{if(2!==u)throw new Error("attribute equal must after attrName");u=3}break;case"'":case'"':if(3===u||1===u){if(1===u&&(a.warning('attribute value must after "="'),l=e.slice(t,c)),t=c+1,!((c=e.indexOf(d,t))>0))throw new Error("attribute value no end '"+d+"' match");s(l,p=e.slice(t,c),t-1),u=5}else{if(4!=u)throw new Error('attribute value must after "="');s(l,p=e.slice(t,c),t),a.warning('attribute "'+l+'" missed start quot('+d+")!!"),t=c+1,u=5}break;case"/":switch(u){case 0:n.setTagName(e.slice(t,c));case 5:case 6:case 7:u=7,n.closed=!0;case 4:case 1:break;case 2:n.closed=!0;break;default:throw new Error("attribute invalid close char('/')")}break;case"":return a.error("unexpected end of input"),0==u&&n.setTagName(e.slice(t,c)),c;case">":switch(u){case 0:n.setTagName(e.slice(t,c));case 5:case 6:case 7:break;case 4:case 1:"/"===(p=e.slice(t,c)).slice(-1)&&(n.closed=!0,p=p.slice(0,-1));case 2:2===u&&(p=l),4==u?(a.warning('attribute "'+p+'" missed quot(")!'),s(l,p,t)):(r.isHTML(i[""])&&p.match(/^(?:disabled|checked|selected)$/i)||a.warning('attribute "'+p+'" missed value!! "'+p+'" instead!!'),s(p,p,t));break;case 3:throw new Error("attribute value missed!!")}return c;case"€":d=" ";default:if(d<=" ")switch(u){case 0:n.setTagName(e.slice(t,c)),u=6;break;case 1:l=e.slice(t,c),u=2;break;case 4:var p=e.slice(t,c);a.warning('attribute "'+p+'" missed quot(")!!'),s(l,p,t);case 5:u=6}else switch(u){case 2:n.tagName,r.isHTML(i[""])&&l.match(/^(?:disabled|checked|selected)$/i)||a.warning('attribute "'+l+'" missed value!! "'+l+'" instead2!!'),s(l,l,t),t=c,u=1;break;case 5:a.warning('attribute space is required"'+l+'"!!');case 6:u=1,t=c;break;case 3:u=4,t=c;break;case 7:throw new Error("elements closed character '/' and '>' must be connected to")}}c++}}function d(e,t,n){for(var i=e.tagName,o=null,a=e.length;a--;){var s=e[a],l=s.qName,c=s.value;if((h=l.indexOf(":"))>0)var u=s.prefix=l.slice(0,h),d=l.slice(h+1),p="xmlns"===u&&d;else d=l,u=null,p="xmlns"===l&&"";s.localName=d,!1!==p&&(null==o&&(o={},f(n,n={})),n[p]=o[p]=c,s.uri=r.XMLNS,t.startPrefixMapping(p,c))}for(a=e.length;a--;)(u=(s=e[a]).prefix)&&("xml"===u&&(s.uri=r.XML),"xmlns"!==u&&(s.uri=n[u||""]));var h;(h=i.indexOf(":"))>0?(u=e.prefix=i.slice(0,h),d=e.localName=i.slice(h+1)):(u=null,d=e.localName=i);var m=e.uri=n[u||""];if(t.startElement(m,d,i,e),!e.closed)return e.currentNSMap=n,e.localNSMap=o,!0;if(t.endElement(m,d,i),o)for(u in o)Object.prototype.hasOwnProperty.call(o,u)&&t.endPrefixMapping(u)}function p(e,t,n,r,i){if(/^(?:script|textarea)$/i.test(n)){var o=e.indexOf("",t),a=e.substring(t+1,o);if(/[&<]/.test(a))return/^script$/i.test(n)?(i.characters(a,0,a.length),o):(a=a.replace(/&#?\w+;/g,r),i.characters(a,0,a.length),o)}return t+1}function h(e,t,n,r){var i=r[n];return null==i&&((i=e.lastIndexOf(""))t?(n.comment(e,t+4,i-t-4),i+3):(r.error("Unclosed comment"),-1):-1;if("CDATA["==e.substr(t+3,6)){var i=e.indexOf("]]>",t+9);return n.startCDATA(),n.characters(e,t+9,i-t-9),n.endCDATA(),i+3}var o=function(e,t){var n,r=[],i=/'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;for(i.lastIndex=t,i.exec(e);n=i.exec(e);)if(r.push(n),n[1])return r}(e,t),a=o.length;if(a>1&&/!doctype/i.test(o[0][0])){var s=o[1][0],l=!1,c=!1;a>3&&(/^public$/i.test(o[2][0])?(l=o[3][0],c=a>4&&o[4][0]):/^system$/i.test(o[2][0])&&(c=o[3][0]));var u=o[a-1];return n.startDTD(s,l,c),n.endDTD(),u.index+u[0].length}return-1}function g(e,t,n){var r=e.indexOf("?>",t);if(r){var i=e.substring(t,r).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);return i?(i[0].length,n.processingInstruction(i[1],i[2]),r+2):-1}return-1}function v(){this.attributeNames={}}s.prototype=new Error,s.prototype.name=s.name,l.prototype={parse:function(e,t,n){var i=this.domBuilder;i.startDocument(),f(t,t={}),function(e,t,n,i,o){function a(e){var t=e.slice(1,-1);return Object.hasOwnProperty.call(n,t)?n[t]:"#"===t.charAt(0)?function(e){if(e>65535){var t=55296+((e-=65536)>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}(parseInt(t.substr(1).replace("x","0x"))):(o.error("entity not found:"+e),e)}function l(t){if(t>k){var n=e.substring(k,t).replace(/&#?\w+;/g,a);w&&f(k),i.characters(n,0,t-k),k=t}}function f(t,n){for(;t>=y&&(n=_.exec(e));)b=n.index,y=b+n[0].length,w.lineNumber++;w.columnNumber=t-b+1}for(var b=0,y=0,_=/.*(?:\r\n?|\n)|.*$/g,w=i.locator,x=[{currentNSMap:t}],T={},k=0;;){try{var S=e.indexOf("<",k);if(S<0){if(!e.substr(k).match(/^\s*$/)){var E=i.doc,C=E.createTextNode(e.substr(k));E.appendChild(C),i.currentElement=C}return}switch(S>k&&l(S),e.charAt(S+1)){case"/":var A=e.indexOf(">",S+3),I=e.substring(S+2,A).replace(/[ \t\n\r]+$/g,""),j=x.pop();A<0?(I=e.substring(S+2).replace(/[\s<].*/,""),o.error("end tag name: "+I+" is not complete:"+j.tagName),A=S+1+I.length):I.match(/\sk?k=A:l(Math.max(S,k)+1)}}(e,t,n,i,this.errorHandler),i.endDocument()}},v.prototype={setTagName:function(e){if(!a.test(e))throw new Error("invalid tagName:"+e);this.tagName=e},addValue:function(e,t,n){if(!a.test(e))throw new Error("invalid attribute:"+e);this.attributeNames[e]=this.length,this[this.length++]={qName:e,value:t,offset:n}},length:0,getLocalName:function(e){return this[e].localName},getLocator:function(e){return this[e].locator},getQName:function(e){return this[e].qName},getURI:function(e){return this[e].uri},getValue:function(e){return this[e].value}},t.XMLReader=l,t.ParseError=s},7353:(e,t,n)=>{"use strict";n.d(t,{FP:()=>o,G6:()=>l,Gi:()=>i,Kt:()=>a,Ll:()=>r,PN:()=>s,PU:()=>u,VU:()=>h,a6:()=>p,eT:()=>f,o6:()=>c,pt:()=>d});var r=!0,i=!1,o="{{",a="}}",s=/\{\{/,l=/\}\}/,c="IN_REVIEW_FILE_DATA",u={CHECKBOX:"checkbox",EMAIL:"email",FILE:"file",HIDDEN:"hidden",INPUT:"input",NUMBER:"number",PASSWORD:"password",RADIO:"radio",SELECT:"select",TEXTAREA:"textarea"},d={AUDIO:"audio",IMAGE:"image",PDF:"pdf",VIDEO:"video"},p={png:d.IMAGE,jpg:d.IMAGE,jpeg:d.IMAGE,gif:d.IMAGE,heic:d.IMAGE,heif:d.IMAGE,webp:d.IMAGE,bmp:d.IMAGE,mkv:d.VIDEO,mp4:d.VIDEO,webm:d.VIDEO,mp3:d.AUDIO,ogg:d.AUDIO,wav:d.AUDIO,pdf:d.PDF},h={mp3:"audio/mpeg",ogg:"audio/ogg",wav:"audio/wav"},f={mkv:"video/x-matroska",mp4:"video/mp4",webm:"video/webm",mov:"video/quicktime",avi:"video/x-msvideo"}},3245:(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";__webpack_require__.d(__webpack_exports__,{CJ:()=>validateFieldValue,JM:()=>ProcedureName,Mm:()=>prepareFormData,NR:()=>WAIT_FOR_AGENT_ID_MSEC,Oj:()=>procedureTokenRegex,Qp:()=>getUrlsFromString,Vt:()=>getFormatStringWithTokensFunction,Yz:()=>prepareRemoteProcedures,dZ:()=>runCustomTrigger,r4:()=>getDefaultFormFieldValue,wE:()=>setPageTitle});var lodash__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(2543),lodash__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_0__),_constants__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(7353);function _typeof(e){return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},_typeof(e)}function _slicedToArray(e,t){return _arrayWithHoles(e)||_iterableToArrayLimit(e,t)||_unsupportedIterableToArray(e,t)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _iterableToArrayLimit(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,i,o,a,s=[],l=!0,c=!1;try{if(o=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;l=!1}else for(;!(l=(r=o.call(n)).done)&&(s.push(r.value),s.length!==t);l=!0);}catch(e){c=!0,i=e}finally{try{if(!l&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(c)throw i}}return s}}function _arrayWithHoles(e){if(Array.isArray(e))return e}function _toConsumableArray(e){return _arrayWithoutHoles(e)||_iterableToArray(e)||_unsupportedIterableToArray(e)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function _unsupportedIterableToArray(e,t){if(e){if("string"==typeof e)return _arrayLikeToArray(e,t);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(e,t):void 0}}function _iterableToArray(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}function _arrayWithoutHoles(e){if(Array.isArray(e))return _arrayLikeToArray(e)}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n{var r,i,o,a;function s(){"use strict";s=function(){return t};var e,t={},n=Object.prototype,r=n.hasOwnProperty,i=Object.defineProperty||function(e,t,n){e[t]=n.value},o="function"==typeof Symbol?Symbol:{},a=o.iterator||"@@iterator",l=o.asyncIterator||"@@asyncIterator",c=o.toStringTag||"@@toStringTag";function u(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{u({},"")}catch(e){u=function(e,t,n){return e[t]=n}}function d(e,t,n,r){var o=t&&t.prototype instanceof b?t:b,a=Object.create(o.prototype),s=new P(r||[]);return i(a,"_invoke",{value:A(e,n,s)}),a}function p(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}t.wrap=d;var h="suspendedStart",f="suspendedYield",m="executing",g="completed",v={};function b(){}function y(){}function _(){}var w={};u(w,a,(function(){return this}));var T=Object.getPrototypeOf,k=T&&T(T(D([])));k&&k!==n&&r.call(k,a)&&(w=k);var S=_.prototype=b.prototype=Object.create(w);function E(e){["next","throw","return"].forEach((function(t){u(e,t,(function(e){return this._invoke(t,e)}))}))}function C(e,t){function n(i,o,a,s){var l=p(e[i],e,o);if("throw"!==l.type){var c=l.arg,u=c.value;return u&&"object"==x(u)&&r.call(u,"__await")?t.resolve(u.__await).then((function(e){n("next",e,a,s)}),(function(e){n("throw",e,a,s)})):t.resolve(u).then((function(e){c.value=e,a(c)}),(function(e){return n("throw",e,a,s)}))}s(l.arg)}var o;i(this,"_invoke",{value:function(e,r){function i(){return new t((function(t,i){n(e,r,t,i)}))}return o=o?o.then(i,i):i()}})}function A(t,n,r){var i=h;return function(o,a){if(i===m)throw Error("Generator is already running");if(i===g){if("throw"===o)throw a;return{value:e,done:!0}}for(r.method=o,r.arg=a;;){var s=r.delegate;if(s){var l=I(s,r);if(l){if(l===v)continue;return l}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if(i===h)throw i=g,r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);i=m;var c=p(t,n,r);if("normal"===c.type){if(i=r.done?g:f,c.arg===v)continue;return{value:c.arg,done:r.done}}"throw"===c.type&&(i=g,r.method="throw",r.arg=c.arg)}}}function I(t,n){var r=n.method,i=t.iterator[r];if(i===e)return n.delegate=null,"throw"===r&&t.iterator.return&&(n.method="return",n.arg=e,I(t,n),"throw"===n.method)||"return"!==r&&(n.method="throw",n.arg=new TypeError("The iterator does not provide a '"+r+"' method")),v;var o=p(i,t.iterator,n.arg);if("throw"===o.type)return n.method="throw",n.arg=o.arg,n.delegate=null,v;var a=o.arg;return a?a.done?(n[t.resultName]=a.value,n.next=t.nextLoc,"return"!==n.method&&(n.method="next",n.arg=e),n.delegate=null,v):a:(n.method="throw",n.arg=new TypeError("iterator result is not an object"),n.delegate=null,v)}function j(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function O(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function P(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(j,this),this.reset(!0)}function D(t){if(t||""===t){var n=t[a];if(n)return n.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var i=-1,o=function n(){for(;++i=0;--o){var a=this.tryEntries[o],s=a.completion;if("root"===a.tryLoc)return i("end");if(a.tryLoc<=this.prev){var l=r.call(a,"catchLoc"),c=r.call(a,"finallyLoc");if(l&&c){if(this.prev=0;--n){var i=this.tryEntries[n];if(i.tryLoc<=this.prev&&r.call(i,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),O(n),v}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var i=r.arg;O(n)}return i}}throw Error("illegal catch attempt")},delegateYield:function(t,n,r){return this.delegate={iterator:D(t),resultName:n,nextLoc:r},"next"===this.method&&(this.arg=e),v}},t}function l(e,t,n,r,i,o,a){try{var s=e[o](a),l=s.value}catch(e){return void n(e)}s.done?t(l):Promise.resolve(l).then(r,i)}function c(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}function u(e,t){return f(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,i,o,a,s=[],l=!0,c=!1;try{if(o=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;l=!1}else for(;!(l=(r=o.call(n)).done)&&(s.push(r.value),s.length!==t);l=!0);}catch(e){c=!0,i=e}finally{try{if(!l&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(c)throw i}}return s}}(e,t)||p(e,t)||d()}function d(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function p(e,t){if(e){if("string"==typeof e)return h(e,t);var n={}.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?h(e,t):void 0}}function h(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&n[1]||""},e.getSecondMatch=function(e,t){var n=t.match(e);return n&&n.length>1&&n[2]||""},e.matchAndReturnConst=function(e,t,n){if(e.test(t))return n},e.getWindowsVersionName=function(e){switch(e){case"NT":return"NT";case"XP":case"NT 5.1":return"XP";case"NT 5.0":return"2000";case"NT 5.2":return"2003";case"NT 6.0":return"Vista";case"NT 6.1":return"7";case"NT 6.2":return"8";case"NT 6.3":return"8.1";case"NT 10.0":return"10";default:return}},e.getMacOSVersionName=function(e){var t=e.split(".").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),10===t[0])switch(t[1]){case 5:return"Leopard";case 6:return"Snow Leopard";case 7:return"Lion";case 8:return"Mountain Lion";case 9:return"Mavericks";case 10:return"Yosemite";case 11:return"El Capitan";case 12:return"Sierra";case 13:return"High Sierra";case 14:return"Mojave";case 15:return"Catalina";default:return}},e.getAndroidVersionName=function(e){var t=e.split(".").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),!(1===t[0]&&t[1]<5))return 1===t[0]&&t[1]<6?"Cupcake":1===t[0]&&t[1]>=6?"Donut":2===t[0]&&t[1]<2?"Eclair":2===t[0]&&2===t[1]?"Froyo":2===t[0]&&t[1]>2?"Gingerbread":3===t[0]?"Honeycomb":4===t[0]&&t[1]<1?"Ice Cream Sandwich":4===t[0]&&t[1]<4?"Jelly Bean":4===t[0]&&t[1]>=4?"KitKat":5===t[0]?"Lollipop":6===t[0]?"Marshmallow":7===t[0]?"Nougat":8===t[0]?"Oreo":9===t[0]?"Pie":void 0},e.getVersionPrecision=function(e){return e.split(".").length},e.compareVersions=function(t,n,r){void 0===r&&(r=!1);var i=e.getVersionPrecision(t),o=e.getVersionPrecision(n),a=Math.max(i,o),s=0,l=e.map([t,n],(function(t){var n=a-e.getVersionPrecision(t),r=t+new Array(n+1).join(".0");return e.map(r.split("."),(function(e){return new Array(20-e.length).join("0")+e})).reverse()}));for(r&&(s=a-Math.min(i,o)),a-=1;a>=s;){if(l[0][a]>l[1][a])return 1;if(l[0][a]===l[1][a]){if(a===s)return 0;a-=1}else if(l[0][a]1?i-1:0),a=1;a0){var a=Object.keys(n),l=s.default.find(a,(function(e){return t.isOS(e)}));if(l){var c=this.satisfies(n[l]);if(void 0!==c)return c}var u=s.default.find(a,(function(e){return t.isPlatform(e)}));if(u){var d=this.satisfies(n[u]);if(void 0!==d)return d}}if(o>0){var p=Object.keys(i),h=s.default.find(p,(function(e){return t.isBrowser(e,!0)}));if(void 0!==h)return this.compareVersion(i[h])}},t.isBrowser=function(e,t){void 0===t&&(t=!1);var n=this.getBrowserName().toLowerCase(),r=e.toLowerCase(),i=s.default.getBrowserTypeByAlias(r);return t&&i&&(r=i.toLowerCase()),r===n},t.compareVersion=function(e){var t=[0],n=e,r=!1,i=this.getBrowserVersion();if("string"==typeof i)return">"===e[0]||"<"===e[0]?(n=e.substr(1),"="===e[1]?(r=!0,n=e.substr(2)):t=[],">"===e[0]?t.push(1):t.push(-1)):"="===e[0]?n=e.substr(1):"~"===e[0]&&(r=!0,n=e.substr(1)),t.indexOf(s.default.compareVersions(i,n,r))>-1},t.isOS=function(e){return this.getOSName(!0)===String(e).toLowerCase()},t.isPlatform=function(e){return this.getPlatformType(!0)===String(e).toLowerCase()},t.isEngine=function(e){return this.getEngineName(!0)===String(e).toLowerCase()},t.is=function(e,t){return void 0===t&&(t=!1),this.isBrowser(e,t)||this.isOS(e)||this.isPlatform(e)},t.some=function(e){var t=this;return void 0===e&&(e=[]),e.some((function(e){return t.is(e)}))},e}();t.default=c,e.exports=t.default},92:function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r,i=(r=n(17))&&r.__esModule?r:{default:r},o=/version\/(\d+(\.?_?\d+)+)/i,a=[{test:[/googlebot/i],describe:function(e){var t={name:"Googlebot"},n=i.default.getFirstMatch(/googlebot\/(\d+(\.\d+))/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/opera/i],describe:function(e){var t={name:"Opera"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/opr\/|opios/i],describe:function(e){var t={name:"Opera"},n=i.default.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/SamsungBrowser/i],describe:function(e){var t={name:"Samsung Internet for Android"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/Whale/i],describe:function(e){var t={name:"NAVER Whale Browser"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/MZBrowser/i],describe:function(e){var t={name:"MZ Browser"},n=i.default.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/focus/i],describe:function(e){var t={name:"Focus"},n=i.default.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/swing/i],describe:function(e){var t={name:"Swing"},n=i.default.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/coast/i],describe:function(e){var t={name:"Opera Coast"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/opt\/\d+(?:.?_?\d+)+/i],describe:function(e){var t={name:"Opera Touch"},n=i.default.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/yabrowser/i],describe:function(e){var t={name:"Yandex Browser"},n=i.default.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/ucbrowser/i],describe:function(e){var t={name:"UC Browser"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/Maxthon|mxios/i],describe:function(e){var t={name:"Maxthon"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/epiphany/i],describe:function(e){var t={name:"Epiphany"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/puffin/i],describe:function(e){var t={name:"Puffin"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/sleipnir/i],describe:function(e){var t={name:"Sleipnir"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/k-meleon/i],describe:function(e){var t={name:"K-Meleon"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/micromessenger/i],describe:function(e){var t={name:"WeChat"},n=i.default.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/qqbrowser/i],describe:function(e){var t={name:/qqbrowserlite/i.test(e)?"QQ Browser Lite":"QQ Browser"},n=i.default.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/msie|trident/i],describe:function(e){var t={name:"Internet Explorer"},n=i.default.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/\sedg\//i],describe:function(e){var t={name:"Microsoft Edge"},n=i.default.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/edg([ea]|ios)/i],describe:function(e){var t={name:"Microsoft Edge"},n=i.default.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/vivaldi/i],describe:function(e){var t={name:"Vivaldi"},n=i.default.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/seamonkey/i],describe:function(e){var t={name:"SeaMonkey"},n=i.default.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/sailfish/i],describe:function(e){var t={name:"Sailfish"},n=i.default.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i,e);return n&&(t.version=n),t}},{test:[/silk/i],describe:function(e){var t={name:"Amazon Silk"},n=i.default.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/phantom/i],describe:function(e){var t={name:"PhantomJS"},n=i.default.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/slimerjs/i],describe:function(e){var t={name:"SlimerJS"},n=i.default.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/blackberry|\bbb\d+/i,/rim\stablet/i],describe:function(e){var t={name:"BlackBerry"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t={name:"WebOS Browser"},n=i.default.getFirstMatch(o,e)||i.default.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/bada/i],describe:function(e){var t={name:"Bada"},n=i.default.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/tizen/i],describe:function(e){var t={name:"Tizen"},n=i.default.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/qupzilla/i],describe:function(e){var t={name:"QupZilla"},n=i.default.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/firefox|iceweasel|fxios/i],describe:function(e){var t={name:"Firefox"},n=i.default.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/electron/i],describe:function(e){var t={name:"Electron"},n=i.default.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/MiuiBrowser/i],describe:function(e){var t={name:"Miui"},n=i.default.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/chromium/i],describe:function(e){var t={name:"Chromium"},n=i.default.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/chrome|crios|crmo/i],describe:function(e){var t={name:"Chrome"},n=i.default.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/GSA/i],describe:function(e){var t={name:"Google Search"},n=i.default.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:function(e){var t=!e.test(/like android/i),n=e.test(/android/i);return t&&n},describe:function(e){var t={name:"Android Browser"},n=i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/playstation 4/i],describe:function(e){var t={name:"PlayStation 4"},n=i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/safari|applewebkit/i],describe:function(e){var t={name:"Safari"},n=i.default.getFirstMatch(o,e);return n&&(t.version=n),t}},{test:[/.*/i],describe:function(e){var t=-1!==e.search("\\(")?/^(.*)\/(.*)[ \t]\((.*)/:/^(.*)\/(.*) /;return{name:i.default.getFirstMatch(t,e),version:i.default.getSecondMatch(t,e)}}}];t.default=a,e.exports=t.default},93:function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r,i=(r=n(17))&&r.__esModule?r:{default:r},o=n(18),a=[{test:[/Roku\/DVP/],describe:function(e){var t=i.default.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i,e);return{name:o.OS_MAP.Roku,version:t}}},{test:[/windows phone/i],describe:function(e){var t=i.default.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i,e);return{name:o.OS_MAP.WindowsPhone,version:t}}},{test:[/windows /i],describe:function(e){var t=i.default.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i,e),n=i.default.getWindowsVersionName(t);return{name:o.OS_MAP.Windows,version:t,versionName:n}}},{test:[/Macintosh(.*?) FxiOS(.*?)\//],describe:function(e){var t={name:o.OS_MAP.iOS},n=i.default.getSecondMatch(/(Version\/)(\d[\d.]+)/,e);return n&&(t.version=n),t}},{test:[/macintosh/i],describe:function(e){var t=i.default.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i,e).replace(/[_\s]/g,"."),n=i.default.getMacOSVersionName(t),r={name:o.OS_MAP.MacOS,version:t};return n&&(r.versionName=n),r}},{test:[/(ipod|iphone|ipad)/i],describe:function(e){var t=i.default.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i,e).replace(/[_\s]/g,".");return{name:o.OS_MAP.iOS,version:t}}},{test:function(e){var t=!e.test(/like android/i),n=e.test(/android/i);return t&&n},describe:function(e){var t=i.default.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i,e),n=i.default.getAndroidVersionName(t),r={name:o.OS_MAP.Android,version:t};return n&&(r.versionName=n),r}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t=i.default.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i,e),n={name:o.OS_MAP.WebOS};return t&&t.length&&(n.version=t),n}},{test:[/blackberry|\bbb\d+/i,/rim\stablet/i],describe:function(e){var t=i.default.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i,e)||i.default.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i,e)||i.default.getFirstMatch(/\bbb(\d+)/i,e);return{name:o.OS_MAP.BlackBerry,version:t}}},{test:[/bada/i],describe:function(e){var t=i.default.getFirstMatch(/bada\/(\d+(\.\d+)*)/i,e);return{name:o.OS_MAP.Bada,version:t}}},{test:[/tizen/i],describe:function(e){var t=i.default.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i,e);return{name:o.OS_MAP.Tizen,version:t}}},{test:[/linux/i],describe:function(){return{name:o.OS_MAP.Linux}}},{test:[/CrOS/],describe:function(){return{name:o.OS_MAP.ChromeOS}}},{test:[/PlayStation 4/],describe:function(e){var t=i.default.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i,e);return{name:o.OS_MAP.PlayStation4,version:t}}}];t.default=a,e.exports=t.default},94:function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r,i=(r=n(17))&&r.__esModule?r:{default:r},o=n(18),a=[{test:[/googlebot/i],describe:function(){return{type:"bot",vendor:"Google"}}},{test:[/huawei/i],describe:function(e){var t=i.default.getFirstMatch(/(can-l01)/i,e)&&"Nova",n={type:o.PLATFORMS_MAP.mobile,vendor:"Huawei"};return t&&(n.model=t),n}},{test:[/nexus\s*(?:7|8|9|10).*/i],describe:function(){return{type:o.PLATFORMS_MAP.tablet,vendor:"Nexus"}}},{test:[/ipad/i],describe:function(){return{type:o.PLATFORMS_MAP.tablet,vendor:"Apple",model:"iPad"}}},{test:[/Macintosh(.*?) FxiOS(.*?)\//],describe:function(){return{type:o.PLATFORMS_MAP.tablet,vendor:"Apple",model:"iPad"}}},{test:[/kftt build/i],describe:function(){return{type:o.PLATFORMS_MAP.tablet,vendor:"Amazon",model:"Kindle Fire HD 7"}}},{test:[/silk/i],describe:function(){return{type:o.PLATFORMS_MAP.tablet,vendor:"Amazon"}}},{test:[/tablet(?! pc)/i],describe:function(){return{type:o.PLATFORMS_MAP.tablet}}},{test:function(e){var t=e.test(/ipod|iphone/i),n=e.test(/like (ipod|iphone)/i);return t&&!n},describe:function(e){var t=i.default.getFirstMatch(/(ipod|iphone)/i,e);return{type:o.PLATFORMS_MAP.mobile,vendor:"Apple",model:t}}},{test:[/nexus\s*[0-6].*/i,/galaxy nexus/i],describe:function(){return{type:o.PLATFORMS_MAP.mobile,vendor:"Nexus"}}},{test:[/[^-]mobi/i],describe:function(){return{type:o.PLATFORMS_MAP.mobile}}},{test:function(e){return"blackberry"===e.getBrowserName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.mobile,vendor:"BlackBerry"}}},{test:function(e){return"bada"===e.getBrowserName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.mobile}}},{test:function(e){return"windows phone"===e.getBrowserName()},describe:function(){return{type:o.PLATFORMS_MAP.mobile,vendor:"Microsoft"}}},{test:function(e){var t=Number(String(e.getOSVersion()).split(".")[0]);return"android"===e.getOSName(!0)&&t>=3},describe:function(){return{type:o.PLATFORMS_MAP.tablet}}},{test:function(e){return"android"===e.getOSName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.mobile}}},{test:function(e){return"macos"===e.getOSName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.desktop,vendor:"Apple"}}},{test:function(e){return"windows"===e.getOSName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.desktop}}},{test:function(e){return"linux"===e.getOSName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.desktop}}},{test:function(e){return"playstation 4"===e.getOSName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.tv}}},{test:function(e){return"roku"===e.getOSName(!0)},describe:function(){return{type:o.PLATFORMS_MAP.tv}}}];t.default=a,e.exports=t.default},95:function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r,i=(r=n(17))&&r.__esModule?r:{default:r},o=n(18),a=[{test:function(e){return"microsoft edge"===e.getBrowserName(!0)},describe:function(e){if(/\sedg\//i.test(e))return{name:o.ENGINE_MAP.Blink};var t=i.default.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i,e);return{name:o.ENGINE_MAP.EdgeHTML,version:t}}},{test:[/trident/i],describe:function(e){var t={name:o.ENGINE_MAP.Trident},n=i.default.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:function(e){return e.test(/presto/i)},describe:function(e){var t={name:o.ENGINE_MAP.Presto},n=i.default.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:function(e){var t=e.test(/gecko/i),n=e.test(/like gecko/i);return t&&!n},describe:function(e){var t={name:o.ENGINE_MAP.Gecko},n=i.default.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}},{test:[/(apple)?webkit\/537\.36/i],describe:function(){return{name:o.ENGINE_MAP.Blink}}},{test:[/(apple)?webkit/i],describe:function(e){var t={name:o.ENGINE_MAP.WebKit},n=i.default.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i,e);return n&&(t.version=n),t}}];t.default=a,e.exports=t.default}})},155:function(t){"use strict";t.exports=e},425:function(e,t,n){"use strict";function r(e,t){return function(){return e.apply(t,arguments)}}var i,o=Object.prototype.toString,a=Object.getPrototypeOf,m=(i=Object.create(null),function(e){var t=o.call(e);return i[t]||(i[t]=t.slice(8,-1).toLowerCase())}),v=function(e){return e=e.toLowerCase(),function(t){return m(t)===e}},y=function(e){return function(t){return x(t)===e}},w=Array.isArray,T=y("undefined"),k=v("ArrayBuffer"),S=y("string"),E=y("function"),C=y("number"),A=function(e){return null!==e&&"object"==x(e)},I=function(e){if("object"!==m(e))return!1;var t=a(e);return!(null!==t&&t!==Object.prototype&&null!==Object.getPrototypeOf(t)||Symbol.toStringTag in e||Symbol.iterator in e)},j=v("Date"),O=v("File"),P=v("Blob"),D=v("FileList"),N=v("URLSearchParams");function L(e,t){var n,r,i=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).allOwnKeys,o=void 0!==i&&i;if(null!=e)if("object"!=x(e)&&(e=[e]),w(e))for(n=0,r=e.length;n0;)if(t===(n=r[i]).toLowerCase())return n;return null}var R,U="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:n.g,F=function(e){return!T(e)&&e!==U},B=(R="undefined"!=typeof Uint8Array&&a(Uint8Array),function(e){return R&&e instanceof R}),z=v("HTMLFormElement"),q=function(){var e=Object.prototype.hasOwnProperty;return function(t,n){return e.call(t,n)}}(),H=v("RegExp"),$=function(e,t){var n=Object.getOwnPropertyDescriptors(e),r={};L(n,(function(n,i){var o;!1!==(o=t(n,i,e))&&(r[i]=o||n)})),Object.defineProperties(e,r)},V="abcdefghijklmnopqrstuvwxyz",W="0123456789",G={DIGIT:W,ALPHA:V,ALPHA_DIGIT:V+V.toUpperCase()+W},X=v("AsyncFunction"),K={isArray:w,isArrayBuffer:k,isBuffer:function(e){return null!==e&&!T(e)&&null!==e.constructor&&!T(e.constructor)&&E(e.constructor.isBuffer)&&e.constructor.isBuffer(e)},isFormData:function(e){var t;return e&&("function"==typeof FormData&&e instanceof FormData||E(e.append)&&("formdata"===(t=m(e))||"object"===t&&E(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&k(e.buffer)},isString:S,isNumber:C,isBoolean:function(e){return!0===e||!1===e},isObject:A,isPlainObject:I,isUndefined:T,isDate:j,isFile:O,isBlob:P,isRegExp:H,isFunction:E,isStream:function(e){return A(e)&&E(e.pipe)},isURLSearchParams:N,isTypedArray:B,isFileList:D,forEach:L,merge:function e(){for(var t=(F(this)&&this||{}).caseless,n={},r=function(r,i){var o=t&&M(n,i)||i;I(n[o])&&I(r)?n[o]=e(n[o],r):I(r)?n[o]=e({},r):w(r)?n[o]=r.slice():n[o]=r},i=0,o=arguments.length;i3&&void 0!==arguments[3]?arguments[3]:{}).allOwnKeys}),e},trim:function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e},inherits:function(e,t,n,r){e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},toFlatObject:function(e,t,n,r){var i,o,s,l={};if(t=t||{},null==e)return t;do{for(o=(i=Object.getOwnPropertyNames(e)).length;o-- >0;)s=i[o],r&&!r(s,e,t)||l[s]||(t[s]=e[s],l[s]=!0);e=!1!==n&&a(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},kindOf:m,kindOfTest:v,endsWith:function(e,t,n){e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;var r=e.indexOf(t,n);return-1!==r&&r===n},toArray:function(e){if(!e)return null;if(w(e))return e;var t=e.length;if(!C(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},forEachEntry:function(e,t){for(var n,r=(e&&e[Symbol.iterator]).call(e);(n=r.next())&&!n.done;){var i=n.value;t.call(e,i[0],i[1])}},matchAll:function(e,t){for(var n,r=[];null!==(n=e.exec(t));)r.push(n);return r},isHTMLForm:z,hasOwnProperty:q,hasOwnProp:q,reduceDescriptors:$,freezeMethods:function(e){$(e,(function(t,n){if(E(e)&&-1!==["arguments","caller","callee"].indexOf(n))return!1;var r=e[n];E(r)&&(t.enumerable=!1,"writable"in t?t.writable=!1:t.set||(t.set=function(){throw Error("Can not rewrite read-only method '"+n+"'")}))}))},toObjectSet:function(e,t){var n={},r=function(e){e.forEach((function(e){n[e]=!0}))};return w(e)?r(e):r(String(e).split(t)),n},toCamelCase:function(e){return e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,(function(e,t,n){return t.toUpperCase()+n}))},noop:function(){},toFiniteNumber:function(e,t){return e=+e,Number.isFinite(e)?e:t},findKey:M,global:U,isContextDefined:F,ALPHABET:G,generateString:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:16,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:G.ALPHA_DIGIT,n="",r=t.length;e--;)n+=t[Math.random()*r|0];return n},isSpecCompliantForm:function(e){return!!(e&&E(e.append)&&"FormData"===e[Symbol.toStringTag]&&e[Symbol.iterator])},toJSONObject:function(e){var t=new Array(10),n=function(e,r){if(A(e)){if(t.indexOf(e)>=0)return;if(!("toJSON"in e)){t[r]=e;var i=w(e)?[]:{};return L(e,(function(e,t){var o=n(e,r+1);!T(o)&&(i[t]=o)})),t[r]=void 0,i}}return e};return n(e,0)},isAsyncFn:X,isThenable:function(e){return e&&(A(e)||E(e))&&E(e.then)&&E(e.catch)}};function Y(e,t,n,r,i){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),i&&(this.response=i)}K.inherits(Y,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:K.toJSONObject(this.config),code:this.code,status:this.response&&this.response.status?this.response.status:null}}});var Q=Y.prototype,J={};function Z(e){return K.isPlainObject(e)||K.isArray(e)}function ee(e){return K.endsWith(e,"[]")?e.slice(0,-2):e}function te(e,t,n){return e?e.concat(t).map((function(e,t){return e=ee(e),!n&&t?"["+e+"]":e})).join(n?".":""):t}["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach((function(e){J[e]={value:e}})),Object.defineProperties(Y,J),Object.defineProperty(Q,"isAxiosError",{value:!0}),Y.from=function(e,t,n,r,i,o){var a=Object.create(Q);return K.toFlatObject(e,a,(function(e){return e!==Error.prototype}),(function(e){return"isAxiosError"!==e})),Y.call(a,e.message,t,n,r,i),a.cause=e,a.name=e.name,o&&Object.assign(a,o),a};var ne=K.toFlatObject(K,{},null,(function(e){return/^is[A-Z]/.test(e)}));function re(e,t,n){if(!K.isObject(e))throw new TypeError("target must be an object");t=t||new FormData;var r=(n=K.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,(function(e,t){return!K.isUndefined(t[e])}))).metaTokens,i=n.visitor||c,o=n.dots,a=n.indexes,s=(n.Blob||"undefined"!=typeof Blob&&Blob)&&K.isSpecCompliantForm(t);if(!K.isFunction(i))throw new TypeError("visitor must be a function");function l(e){if(null===e)return"";if(K.isDate(e))return e.toISOString();if(!s&&K.isBlob(e))throw new Y("Blob is not supported. Use a Buffer instead.");return K.isArrayBuffer(e)||K.isTypedArray(e)?s&&"function"==typeof Blob?new Blob([e]):Buffer.from(e):e}function c(e,n,i){var s=e;if(e&&!i&&"object"==x(e))if(K.endsWith(n,"{}"))n=r?n:n.slice(0,-2),e=JSON.stringify(e);else if(K.isArray(e)&&function(e){return K.isArray(e)&&!e.some(Z)}(e)||(K.isFileList(e)||K.endsWith(n,"[]"))&&(s=K.toArray(e)))return n=ee(n),s.forEach((function(e,r){!K.isUndefined(e)&&null!==e&&t.append(!0===a?te([n],r,o):null===a?n:n+"[]",l(e))})),!1;return!!Z(e)||(t.append(te(i,n,o),l(e)),!1)}var u=[],d=Object.assign(ne,{defaultVisitor:c,convertValue:l,isVisitable:Z});if(!K.isObject(e))throw new TypeError("data must be an object");return function e(n,r){if(!K.isUndefined(n)){if(-1!==u.indexOf(n))throw Error("Circular reference detected in "+r.join("."));u.push(n),K.forEach(n,(function(n,o){!0===(!(K.isUndefined(n)||null===n)&&i.call(t,n,K.isString(o)?o.trim():o,r,d))&&e(n,r?r.concat(o):[o])})),u.pop()}}(e),t}function ie(e){var t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,(function(e){return t[e]}))}function oe(e,t){this._pairs=[],e&&re(e,this,t)}var ae=oe.prototype;function se(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function le(e,t,n){if(!t)return e;var r,i=n&&n.encode||se,o=n&&n.serialize;if(r=o?o(t,n):K.isURLSearchParams(t)?t.toString():new oe(t,n).toString(i)){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+r}return e}ae.append=function(e,t){this._pairs.push([e,t])},ae.toString=function(e){var t=e?function(t){return e.call(this,t,ie)}:ie;return this._pairs.map((function(e){return t(e[0])+"="+t(e[1])}),"").join("&")};var ce,ue=function(){return _((function e(){b(this,e),this.handlers=[]}),[{key:"use",value:function(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!n&&n.synchronous,runWhen:n?n.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(e){this.handlers[e]&&(this.handlers[e]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(e){K.forEach(this.handlers,(function(t){null!==t&&e(t)}))}}])}(),de={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},pe={isBrowser:!0,classes:{URLSearchParams:"undefined"!=typeof URLSearchParams?URLSearchParams:oe,FormData:"undefined"!=typeof FormData?FormData:null,Blob:"undefined"!=typeof Blob?Blob:null},protocols:["http","https","file","blob","url","data"]},he="undefined"!=typeof window&&"undefined"!=typeof document,fe=(ce="undefined"!=typeof navigator&&navigator.product,he&&["ReactNative","NativeScript","NS"].indexOf(ce)<0),me="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&"function"==typeof self.importScripts,ge=g(g({},Object.freeze({__proto__:null,hasBrowserEnv:he,hasStandardBrowserWebWorkerEnv:me,hasStandardBrowserEnv:fe})),pe);function ve(e){function t(e,n,r,i){var o=e[i++];if("__proto__"===o)return!0;var a=Number.isFinite(+o),s=i>=e.length;return o=!o&&K.isArray(r)?r.length:o,s?(K.hasOwnProp(r,o)?r[o]=[r[o],n]:r[o]=n,!a):(r[o]&&K.isObject(r[o])||(r[o]=[]),t(e,n,r[o],i)&&K.isArray(r[o])&&(r[o]=function(e){var t,n,r={},i=Object.keys(e),o=i.length;for(t=0;t-1,o=K.isObject(e);if(o&&K.isHTMLForm(e)&&(e=new FormData(e)),K.isFormData(e))return i?JSON.stringify(ve(e)):e;if(K.isArrayBuffer(e)||K.isBuffer(e)||K.isStream(e)||K.isFile(e)||K.isBlob(e))return e;if(K.isArrayBufferView(e))return e.buffer;if(K.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();if(o){if(r.indexOf("application/x-www-form-urlencoded")>-1)return function(e,t){return re(e,new ge.classes.URLSearchParams,Object.assign({visitor:function(e,t,n,r){return ge.isNode&&K.isBuffer(e)?(this.append(t,e.toString("base64")),!1):r.defaultVisitor.apply(this,arguments)}},t))}(e,this.formSerializer).toString();if((n=K.isFileList(e))||r.indexOf("multipart/form-data")>-1){var a=this.env&&this.env.FormData;return re(n?{"files[]":e}:e,a&&new a,this.formSerializer)}}return o||i?(t.setContentType("application/json",!1),function(e){if(K.isString(e))try{return(0,JSON.parse)(e),K.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(0,JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||be.transitional,n=t&&t.forcedJSONParsing,r="json"===this.responseType;if(e&&K.isString(e)&&(n&&!this.responseType||r)){var i=!(t&&t.silentJSONParsing)&&r;try{return JSON.parse(e)}catch(e){if(i){if("SyntaxError"===e.name)throw Y.from(e,Y.ERR_BAD_RESPONSE,this,null,this.response);throw e}}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:ge.classes.FormData,Blob:ge.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};K.forEach(["delete","get","head","post","put","patch"],(function(e){be.headers[e]={}}));var ye=be,_e=K.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),we=Symbol("internals");function xe(e){return e&&String(e).trim().toLowerCase()}function Te(e){return!1===e||null==e?e:K.isArray(e)?e.map(Te):String(e)}function ke(e,t,n,r,i){return K.isFunction(r)?r.call(this,t,n):(i&&(t=n),K.isString(t)?K.isString(r)?-1!==t.indexOf(r):K.isRegExp(r)?r.test(t):void 0:void 0)}var Se=function(){return _((function e(t){b(this,e),t&&this.set(t)}),[{key:"set",value:function(e,t,n){var r=this;function i(e,t,n){var i=xe(t);if(!i)throw new Error("header name must be a non-empty string");var o=K.findKey(r,i);(!o||void 0===r[o]||!0===n||void 0===n&&!1!==r[o])&&(r[o||t]=Te(e))}var o=function(e,t){return K.forEach(e,(function(e,n){return i(e,n,t)}))};return K.isPlainObject(e)||e instanceof this.constructor?o(e,t):K.isString(e)&&(e=e.trim())&&!/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim())?o(function(e){var t,n,r,i={};return e&&e.split("\n").forEach((function(e){r=e.indexOf(":"),t=e.substring(0,r).trim().toLowerCase(),n=e.substring(r+1).trim(),!t||i[t]&&_e[t]||("set-cookie"===t?i[t]?i[t].push(n):i[t]=[n]:i[t]=i[t]?i[t]+", "+n:n)})),i}(e),t):null!=e&&i(t,e,n),this}},{key:"get",value:function(e,t){if(e=xe(e)){var n=K.findKey(this,e);if(n){var r=this[n];if(!t)return r;if(!0===t)return function(e){for(var t,n=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;t=r.exec(e);)n[t[1]]=t[2];return n}(r);if(K.isFunction(t))return t.call(this,r,n);if(K.isRegExp(t))return t.exec(r);throw new TypeError("parser must be boolean|regexp|function")}}}},{key:"has",value:function(e,t){if(e=xe(e)){var n=K.findKey(this,e);return!(!n||void 0===this[n]||t&&!ke(0,this[n],n,t))}return!1}},{key:"delete",value:function(e,t){var n=this,r=!1;function i(e){if(e=xe(e)){var i=K.findKey(n,e);!i||t&&!ke(0,n[i],i,t)||(delete n[i],r=!0)}}return K.isArray(e)?e.forEach(i):i(e),r}},{key:"clear",value:function(e){for(var t=Object.keys(this),n=t.length,r=!1;n--;){var i=t[n];e&&!ke(0,this[i],i,e,!0)||(delete this[i],r=!0)}return r}},{key:"normalize",value:function(e){var t=this,n={};return K.forEach(this,(function(r,i){var o=K.findKey(n,i);if(o)return t[o]=Te(r),void delete t[i];var a=e?function(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(function(e,t,n){return t.toUpperCase()+n}))}(i):String(i).trim();a!==i&&delete t[i],t[a]=Te(r),n[a]=!0})),this}},{key:"concat",value:function(){for(var e,t=arguments.length,n=new Array(t),r=0;r1?n-1:0),i=1;i1?"since :\n"+s.map(Le).join("\n"):" "+Le(s[0]):"as no adapter specified"),"ERR_NOT_SUPPORT")}return n};function Ue(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new Ie(null,e)}function Fe(e){return Ue(e),e.headers=Ee.from(e.headers),e.data=Ce.call(e,e.transformRequest),-1!==["post","put","patch"].indexOf(e.method)&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Re(e.adapter||ye.adapter)(e).then((function(t){return Ue(e),t.data=Ce.call(e,e.transformResponse,t),t.headers=Ee.from(t.headers),t}),(function(t){return Ae(t)||(Ue(e),t&&t.response&&(t.response.data=Ce.call(e,e.transformResponse,t.response),t.response.headers=Ee.from(t.response.headers))),Promise.reject(t)}))}var Be=function(e){return e instanceof Ee?e.toJSON():e};function ze(e,t){t=t||{};var n={};function r(e,t,n){return K.isPlainObject(e)&&K.isPlainObject(t)?K.merge.call({caseless:n},e,t):K.isPlainObject(t)?K.merge({},t):K.isArray(t)?t.slice():t}function i(e,t,n){return K.isUndefined(t)?K.isUndefined(e)?void 0:r(void 0,e,n):r(e,t,n)}function o(e,t){if(!K.isUndefined(t))return r(void 0,t)}function a(e,t){return K.isUndefined(t)?K.isUndefined(e)?void 0:r(void 0,e):r(void 0,t)}function s(n,i,o){return o in t?r(n,i):o in e?r(void 0,n):void 0}var l={url:o,method:o,data:o,baseURL:a,transformRequest:a,transformResponse:a,paramsSerializer:a,timeout:a,timeoutMessage:a,withCredentials:a,withXSRFToken:a,adapter:a,responseType:a,xsrfCookieName:a,xsrfHeaderName:a,onUploadProgress:a,onDownloadProgress:a,decompress:a,maxContentLength:a,maxBodyLength:a,beforeRedirect:a,transport:a,httpAgent:a,httpsAgent:a,cancelToken:a,socketPath:a,responseEncoding:a,validateStatus:s,headers:function(e,t){return i(Be(e),Be(t),!0)}};return K.forEach(Object.keys(Object.assign({},e,t)),(function(r){var o=l[r]||i,a=o(e[r],t[r],r);K.isUndefined(a)&&o!==s||(n[r]=a)})),n}var qe={};["object","boolean","number","function","string","symbol"].forEach((function(e,t){qe[e]=function(n){return x(n)===e||"a"+(t<1?"n ":" ")+e}}));var He={};qe.transitional=function(e,t,n){function r(e,t){return"[Axios v1.6.7] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,i,o){if(!1===e)throw new Y(r(i," has been removed"+(t?" in "+t:"")),Y.ERR_DEPRECATED);return t&&!He[i]&&(He[i]=!0,console.warn(r(i," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,i,o)}};var $e={assertOptions:function(e,t,n){if("object"!=x(e))throw new Y("options must be an object",Y.ERR_BAD_OPTION_VALUE);for(var r=Object.keys(e),i=r.length;i-- >0;){var o=r[i],a=t[o];if(a){var s=e[o],l=void 0===s||a(s,o,e);if(!0!==l)throw new Y("option "+o+" must be "+l,Y.ERR_BAD_OPTION_VALUE)}else if(!0!==n)throw new Y("Unknown option "+o,Y.ERR_BAD_OPTION)}},validators:qe},Ve=$e.validators,We=function(){return _((function e(t){b(this,e),this.defaults=t,this.interceptors={request:new ue,response:new ue}}),[{key:"request",value:(e=function(e){return function(){var t=this,n=arguments;return new Promise((function(r,i){var o=e.apply(t,n);function a(e){l(o,r,i,a,s,"next",e)}function s(e){l(o,r,i,a,s,"throw",e)}a(void 0)}))}}(s().mark((function e(t,n){var r,i;return s().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,this._request(t,n);case 3:return e.abrupt("return",e.sent);case 6:throw e.prev=6,e.t0=e.catch(0),e.t0 instanceof Error&&(Error.captureStackTrace?Error.captureStackTrace(r={}):r=new Error,i=r.stack?r.stack.replace(/^.+\n/,""):"",e.t0.stack?i&&!String(e.t0.stack).endsWith(i.replace(/^.+\n.+\n/,""))&&(e.t0.stack+="\n"+i):e.t0.stack=i),e.t0;case 10:case"end":return e.stop()}}),e,this,[[0,6]])}))),function(t,n){return e.apply(this,arguments)})},{key:"_request",value:function(e,t){"string"==typeof e?(t=t||{}).url=e:t=e||{};var n=t=ze(this.defaults,t),r=n.transitional,i=n.paramsSerializer,o=n.headers;void 0!==r&&$e.assertOptions(r,{silentJSONParsing:Ve.transitional(Ve.boolean),forcedJSONParsing:Ve.transitional(Ve.boolean),clarifyTimeoutError:Ve.transitional(Ve.boolean)},!1),null!=i&&(K.isFunction(i)?t.paramsSerializer={serialize:i}:$e.assertOptions(i,{encode:Ve.function,serialize:Ve.function},!0)),t.method=(t.method||this.defaults.method||"get").toLowerCase();var a=o&&K.merge(o.common,o[t.method]);o&&K.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete o[e]})),t.headers=Ee.concat(a,o);var s=[],l=!0;this.interceptors.request.forEach((function(e){"function"==typeof e.runWhen&&!1===e.runWhen(t)||(l=l&&e.synchronous,s.unshift(e.fulfilled,e.rejected))}));var c,u=[];this.interceptors.response.forEach((function(e){u.push(e.fulfilled,e.rejected)}));var d,p=0;if(!l){var h=[Fe.bind(this),void 0];for(h.unshift.apply(h,s),h.push.apply(h,u),d=h.length,c=Promise.resolve(t);p0;)r._listeners[t](e);r._listeners=null}})),this.promise.then=function(e){var t,n=new Promise((function(e){r.subscribe(e),t=e})).then(e);return n.cancel=function(){r.unsubscribe(t)},n},t((function(e,t,i){r.reason||(r.reason=new Ie(e,t,i),n(r.reason))}))}return _(e,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]}},{key:"unsubscribe",value:function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}}}],[{key:"source",value:function(){var t;return{token:new e((function(e){t=e})),cancel:t}}}])}(),Ke=Xe,Ye={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Ye).forEach((function(e){var t=u(e,2),n=t[0],r=t[1];Ye[r]=n}));var Qe=Ye,Je=function e(t){var n=new Ge(t),i=r(Ge.prototype.request,n);return K.extend(i,Ge.prototype,n,{allOwnKeys:!0}),K.extend(i,n,null,{allOwnKeys:!0}),i.create=function(n){return e(ze(t,n))},i}(ye);Je.Axios=Ge,Je.CanceledError=Ie,Je.CancelToken=Ke,Je.isCancel=Ae,Je.VERSION="1.6.7",Je.toFormData=re,Je.AxiosError=Y,Je.Cancel=Je.CanceledError,Je.all=function(e){return Promise.all(e)},Je.spread=function(e){return function(t){return e.apply(null,t)}},Je.isAxiosError=function(e){return K.isObject(e)&&!0===e.isAxiosError},Je.mergeConfig=ze,Je.AxiosHeaders=Ee,Je.formToJSON=function(e){return ve(K.isHTMLForm(e)?new FormData(e):e)},Je.getAdapter=Re,Je.HttpStatusCode=Qe,Je.default=Je,e.exports=Je}},n={};function r(e){var i=n[e];if(void 0!==i)return i.exports;var o=n[e]={exports:{}};return t[e].call(o.exports,o,o.exports,r),o.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.g=function(){if("object"==("undefined"==typeof globalThis?"undefined":x(globalThis)))return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==("undefined"==typeof window?"undefined":x(window)))return window}}(),r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};return function(){"use strict";r.r(i),r.d(i,{AGENT_STATUS:function(){return pe},CONNECTION_STATUS:function(){return X},ErrorBoundary:function(){return O},MephistoContext:function(){return N},STATUS_TO_TEXT_MAP:function(){return he},axiosInstance:function(){return m},doesSupportWebsockets:function(){return w},getBlockedExplanation:function(){return j},getTaskConfig:function(){return T},isMobile:function(){return y},libVersion:function(){return P},postCompleteOnboarding:function(){return E},postCompleteTask:function(){return C},postData:function(){return v},postErrorLog:function(){return I},postMetadata:function(){return A},postMultipartData:function(){return b},postProviderRequest:function(){return k},pythonTime:function(){return D},requestAgent:function(){return S},useMephistoLiveTask:function(){return fe},useMephistoRemoteProcedureTask:function(){return we},useMephistoSocket:function(){return ie},useMephistoTask:function(){return Ee}});var e=r(155),t=r.n(e),n=r(880),o=r.n(n);function a(e){return a="function"==typeof Symbol&&"symbol"==x(Symbol.iterator)?function(e){return x(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":x(e)},a(e)}function s(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(e){}return(s=function(){return!!e})()}function l(e){return l=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},l(e)}function c(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function u(e,t){return u=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},u(e,t)}function d(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function p(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:"",method:"POST",headers:{"Content-Type":"application/json"},data:arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}}).then((function(e){return e.data}))}function b(e,t){return m({url:e,method:"POST",headers:{"Content-Type":"multipart/form-data"},data:t}).then((function(e){return e.data}))}function y(){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}m.interceptors.request.use((function(e){var t=function(){try{return getProviderURLParams?"function"==typeof getProviderURLParams?getProviderURLParams():getProviderURLParams:null}catch(e){if(e instanceof ReferenceError)return null;throw e}}();return t?(e.params=p(p({},e.params),t),e):e}));var _=o().getParser(window.navigator.userAgent);function w(){return _.satisfies({"internet explorer":">=10",chrome:">=16",firefox:">=11",opera:">=12.1",safari:">=7","android browser":">=3"})}function T(){return m("/task_config.json",{params:{mephisto_task_version:P}}).then((function(e){var t=e.data;return t.mephisto_task_version!==P&&console.warn("Version mismatch detected! Local `mephisto-task` package is on version "+P+" but the server expected version "+t.mephisto_task_version+". Please ensure you are using the package version expected by the Mephisto backend."),e.data}))}function k(e,t){return v(new URL(window.location.origin+e).toString(),{provider_data:t,client_timestamp:D()})}function S(){return k("/request_agent",getAgentRegistration())}function E(e,t){return k(g.submitOnboarding,{USED_AGENT_ID:e,onboarding_data:t})}function C(e,t,n){var r=D();if(n){var i=t;return i.append("USED_AGENT_ID",e),i.append("client_timestamp",r),b(g.submitTask,i).then((function(e){return handleSubmitToProvider(i.get("final_string_data")||i.get("final_data")),e})).then((function(e){console.log("Submitted")}))}return v(g.submitTask,{USED_AGENT_ID:e,final_data:t,client_timestamp:r}).then((function(e){return handleSubmitToProvider(t),e})).then((function(e){console.log("Submitted")}))}function A(e,t,n){var r=D();if(n){var i=t;return i.set("USED_AGENT_ID",e),i.set("metadata",t.get("data")),i.set("client_timestamp",r),b(g.submitMetadata,i).then((function(e){return console.log("Metadata submitted"),e}))}return v(g.submitMetadata,{USED_AGENT_ID:e,metadata:t,client_timestamp:r}).then((function(e){return console.log("Metadata submitted"),e}))}function I(e,t){return v(g.logError,{USED_AGENT_ID:e,error_data:t,client_timestamp:D()}).then((function(e){}))}function j(e){var t={no_mobile:"Sorry, this task cannot be completed on mobile devices. Please use a computer.",no_websockets:"Sorry, your browser does not support the required version of websockets for this task. Please upgrade to a modern browser."};return e in t?t[e]:"Sorry, you are not able to work on this task. (code: ".concat(e,")")}var O=function(e){function n(){var e;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n);for(var t=arguments.length,r=new Array(t),i=0;ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t&&console.log(e)}function re(e,t){return null==e?t:e}function ie(e){var n=e.onConnectionStatusChange,r=e.onLiveUpdate,i=e.onStatusUpdate,o=e.config,a=void 0===o?{}:o,s={heartbeat_id:null,socket_terminated:!1,setting_socket:!1,heartbeats_without_response:0,last_mephisto_ping:Date.now()},l=t().useReducer((function(e,t){return M(M({},e),t)}),s),c=R(l,2),u=c[0],d=c[1],p=t().useRef(),h=t().useRef(new ee),f=t().useRef(),m=t().useRef([]);function g(){if(!u.socket_terminated&&h.current.size()>0&&Date.now()>h.current.peek()[1]){var e=R(h.current.pop(),2),t=e[0],n=e[1];v(t)||h.current.push(t,n)}}function v(e){if(0===p.current.readyState)return!1;if(p.current.readyState>1)return ne("Socket not in ready state, restarting if possible",2),b(),!1;try{return p.current.send(JSON.stringify(e.packet)),void 0!==e.callback&&e.callback(e.packet),!0}catch(e){return b(),!1}}function b(){setTimeout((function(){try{p.current.close()}catch(e){ne("Server had error "+e+" when closing after an error",1)}f.current.setupWebsocket()}),0)}function y(e,t,n){var r=Date.now();void 0===t.update_id&&(t.update_id="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){var t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)})));var i={packet:{packet_type:e,subject_id:u.agentId,data:t,client_timestamp:D()},callback:n};h.current.push(i,r)}function _(){if(!u.setting_socket&&!u.socket_terminated){d({setting_socket:!0}),window.setTimeout((function(){return d({setting_socket:!1})}),4e3);var e=window.location,t=("https:"===e.protocol?"wss://":"ws://")+e.hostname+":"+e.port;p.current=new WebSocket(t),p.current.onmessage=function(e){!function(e){if(e.packet_type===W){var t=m.current;if(t.includes(e.data.update_id))return void ne("Skipping existing update_id "+e.data.update_id,3);m.current=[].concat(function(e){if(Array.isArray(e))return F(e)}(n=t)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(n)||U(n)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),[e.data.update_id]),r(e.data)}else e.packet_type===V?i(e.data):e.packet_type===H&&d({last_mephisto_ping:e.data.last_mephisto_ping,heartbeats_without_response:0});var n}(JSON.parse(e.data))},p.current.onopen=function(){if(ne("Server connected.",2),f.current.enqueuePacket($,{},(function(){n(X.CONNECTED)})),window.setTimeout((function(){1===p.current.readyState||u.socket_terminated||n(X.FAILED)}),1e4),window.setTimeout((function(){return f.current.sendHeartbeat()}),500),null==u.heartbeat_id){var e=window.setInterval((function(){return f.current.heartbeatThread()}),re(a.heartbeatTime,J));d({heartbeat_id:e})}d({setting_socket:!1})},p.current.onerror=function(e){b()},p.current.onclose=function(){ne("Server closing.",3),n(X.DISCONNECTED)}}}function w(){return u.socket_terminated?(window.clearInterval(u.heartbeat_id),void d({heartbeat_id:null})):(u.heartbeats_without_response===re(a.refreshSocketMissedResponses,Q)&&(n(X.RECONNECTING_ROUTER),b()),u.heartbeats_without_response>=re(a.routerDeadTimeout,Z)&&(n(X.DISCONNECTED_ROUTER),f.current.closeSocket()),Date.now()-u.last_mephisto_ping>re(a.connectionDeadMephistoPing,Y)?(f.current.closeSocket(),i({status:q}),n(X.DISCONNECTED_SERVER),window.clearInterval(u.heartbeat_id),void d({heartbeat_id:null})):void f.current.sendHeartbeat())}function x(){v({packet:{packet_type:H,subject_id:u.agentId,client_timestamp:D()}}),d({heartbeats_without_response:u.heartbeats_without_response+1})}function T(){u.socket_terminated?ne("Socket already closed",2):(ne("Socket closing",3),p.current.close(),d({socket_terminated:!0}))}return t().useEffect((function(){f.current={sendingThread:g,heartbeatThread:w,closeSocket:T,setupWebsocket:_,enqueuePacket:y,sendHeartbeat:x}})),{connect:function(e){n(X.INITIALIZING),f.current.setupWebsocket(),f.current.sendingThread();var t=window.setInterval((function(){return f.current.sendingThread()}),re(a.sendThreadRefresh,K));d({agentId:e,messageSenderThreadId:t})},destroy:function(){return f.current.closeSocket()},sendLiveUpdate:function(e){return new Promise((function(t){f.current.enqueuePacket(G,e,(function(e){t(e.data)}))}))}}}function oe(e){return oe="function"==typeof Symbol&&"symbol"==x(Symbol.iterator)?function(e){return x(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":x(e)},oe(e)}function ae(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function se(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){function t(t){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}((function(e){throw e})),f:r}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,o=!0,a=!1;return{s:function(){t=t.call(e)},n:function(){var e=t.next();return o=e.done,e},e:function(e){function t(t){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}((function(e){a=!0,i=e})),f:function(){try{o||null==t.return||t.return()}finally{if(a)throw i}}}}(d.past_live_updates);try{for(t.s();!(e=t.n()).done;)p(e.value)}catch(e){t.e(e)}finally{t.f()}}}),[d]);var h=c({onConnectionStatusChange:function(e){i(e)},onStatusUpdate:function(t){var n=t.status;s(n),e.onStatusUpdate&&e.onStatusUpdate({status:n})},onLiveUpdate:p});return w()||(u.blockedReason="no_websockets"),se(se(se({},u),h),{},{connectionStatus:r,agentStatus:a})};function me(e){return me="function"==typeof Symbol&&"symbol"==x(Symbol.iterator)?function(e){return x(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":x(e)},me(e)}var ge=["connect","destroy","sendLiveUpdate","agentId"];function ve(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function be(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}(c,ge),m={connect:u,destroy:d,sendLiveUpdate:p};t().useEffect((function(){h&&(console.log("connecting..."),u(h),s(h))}),[h]);var g=t().useCallback((function(e){var t=e.targetEvent,n=e.args,r=e.callback,i="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(e){var t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})),o={request_id:i,target:t,args:JSON.stringify(n)};return p(o).then((function(e){void 0!==r&&(e.callback=r,e.args=n,l.current[i]=e)})),i}),[h]);return be(be({},f),{},{agentId:a,remoteProcedure:function(e){var t=function(t){return new Promise((function(n,i){void 0!==r?i({disconnected:!0,reason:r}):g({targetEvent:e,args:t,callback:n})}))};return t.invoke=t,t},disconnectIssueText:r,_fullSocketProps:m})};function xe(e){return xe="function"==typeof Symbol&&"symbol"==x(Symbol.iterator)?function(e){return x(e)}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":x(e)},xe(e)}function Te(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?arguments[1]:void 0,a=o?Number(o):0;a!=a&&(a=0);var s=Math.min(Math.max(a,0),n);if(i+s>n)return!1;for(var c=-1;++c]+>/g,"")),r&&(l=S(l)),l=l.toUpperCase(),o="contains"===n?l.indexOf(t)>=0:l.startsWith(t)))break}return o}function _(e){return parseInt(e,10)||0}e.fn.triggerNative=function(e){var t,n=this[0];n.dispatchEvent?(b?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),n.dispatchEvent(t)):n.fireEvent?((t=document.createEventObject()).eventType=e,n.fireEvent("on"+e,t)):this.trigger(e)};var w={À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"},x=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,T=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function k(e){return w[e]}function S(e){return(e=e.toString())&&e.replace(x,k).replace(T,"")}var E,C,A,I,j,O=(E={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},C=function(e){return E[e]},A="(?:"+Object.keys(E).join("|")+")",I=RegExp(A),j=RegExp(A,"g"),function(e){return e=null==e?"":""+e,I.test(e)?e.replace(j,C):e}),P={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},D=27,N=13,L=32,M=9,R=38,U=40,F={success:!1,major:"3"};try{F.full=(e.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split("."),F.major=F.full[0],F.success=!0}catch(e){}var B=0,z=".bs.select",q={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},H={MENU:"."+q.MENU},$={div:document.createElement("div"),span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode(" "),fragment:document.createDocumentFragment()};$.noResults=$.li.cloneNode(!1),$.noResults.className="no-results",$.a.setAttribute("role","option"),$.a.className="dropdown-item",$.subtext.className="text-muted",$.text=$.span.cloneNode(!1),$.text.className="text",$.checkMark=$.span.cloneNode(!1);var V=new RegExp(R+"|"+U),W=new RegExp("^"+M+"$|"+D),G={li:function(e,t,n){var r=$.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?r.appendChild(e):r.innerHTML=e),void 0!==t&&""!==t&&(r.className=t),null!=n&&r.classList.add("optgroup-"+n),r},a:function(e,t,n){var r=$.a.cloneNode(!0);return e&&(11===e.nodeType?r.appendChild(e):r.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&r.classList.add.apply(r.classList,t.split(/\s+/)),n&&r.setAttribute("style",n),r},text:function(e,t){var n,r,i=$.text.cloneNode(!1);if(e.content)i.innerHTML=e.content;else{if(i.textContent=e.text,e.icon){var o=$.whitespace.cloneNode(!1);(r=(!0===t?$.i:$.span).cloneNode(!1)).className=this.options.iconBase+" "+e.icon,$.fragment.appendChild(r),$.fragment.appendChild(o)}e.subtext&&((n=$.subtext.cloneNode(!1)).textContent=e.subtext,i.appendChild(n))}if(!0===t)for(;i.childNodes.length>0;)$.fragment.appendChild(i.childNodes[0]);else $.fragment.appendChild(i);return $.fragment},label:function(e){var t,n,r=$.text.cloneNode(!1);if(r.innerHTML=e.display,e.icon){var i=$.whitespace.cloneNode(!1);(n=$.span.cloneNode(!1)).className=this.options.iconBase+" "+e.icon,$.fragment.appendChild(n),$.fragment.appendChild(i)}return e.subtext&&((t=$.subtext.cloneNode(!1)).textContent=e.subtext,r.appendChild(t)),$.fragment.appendChild(r),$.fragment}};function X(e,t){e.length||($.noResults.innerHTML=this.options.noneResultsText.replace("{0}",'"'+O(t)+'"'),this.$menuInner[0].firstChild.appendChild($.noResults))}var K=function(t,n){var r=this;g.useDefault||(e.valHooks.select.set=g._set,g.useDefault=!0),this.$element=e(t),this.$newElement=null,this.$button=null,this.$menu=null,this.options=n,this.selectpicker={main:{},search:{},current:{},view:{},isSearching:!1,keydown:{keyHistory:"",resetKeyHistory:{start:function(){return setTimeout((function(){r.selectpicker.keydown.keyHistory=""}),800)}}}},this.sizeInfo={},null===this.options.title&&(this.options.title=this.$element.attr("title"));var i=this.options.windowPadding;"number"==typeof i&&(this.options.windowPadding=[i,i,i,i]),this.val=K.prototype.val,this.render=K.prototype.render,this.refresh=K.prototype.refresh,this.setStyle=K.prototype.setStyle,this.selectAll=K.prototype.selectAll,this.deselectAll=K.prototype.deselectAll,this.destroy=K.prototype.destroy,this.remove=K.prototype.remove,this.show=K.prototype.show,this.hide=K.prototype.hide,this.init()};function Y(n){var r,i=arguments,o=n;if([].shift.apply(i),!F.success){try{F.full=(e.fn.dropdown.Constructor.VERSION||"").split(" ")[0].split(".")}catch(e){K.BootstrapVersion?F.full=K.BootstrapVersion.split(" ")[0].split("."):(F.full=[F.major,"0","0"],console.warn("There was an issue retrieving Bootstrap's version. Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.",e))}F.major=F.full[0],F.success=!0}if("4"===F.major){var a=[];K.DEFAULTS.style===q.BUTTONCLASS&&a.push({name:"style",className:"BUTTONCLASS"}),K.DEFAULTS.iconBase===q.ICONBASE&&a.push({name:"iconBase",className:"ICONBASE"}),K.DEFAULTS.tickIcon===q.TICKICON&&a.push({name:"tickIcon",className:"TICKICON"}),q.DIVIDER="dropdown-divider",q.SHOW="show",q.BUTTONCLASS="btn-light",q.POPOVERHEADER="popover-header",q.ICONBASE="",q.TICKICON="bs-ok-default";for(var s=0;s'},maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]}},K.prototype={constructor:K,init:function(){var e=this,t=this.$element.attr("id"),n=this.$element[0],r=n.form;B++,this.selectId="bs-select-"+B,n.classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),n.classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.buildData(),this.$element.after(this.$newElement).prependTo(this.$newElement),r&&null===n.form&&(r.id||(r.id="form-"+this.selectId),n.setAttribute("form",r.id)),this.$button=this.$newElement.children("button"),this.$menu=this.$newElement.children(H.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),n.classList.remove("bs-select-hidden"),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(q.MENURIGHT),void 0!==t&&this.$button.attr("data-id",t),this.checkDisabled(),this.clickListener(),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.render(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+z,(function(){if(e.isVirtual()){var t=e.$menuInner[0],n=t.firstChild.cloneNode(!1);t.replaceChild(n,t.firstChild),t.scrollTop=0}})),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(t){e.$element.trigger("hide"+z,t)},"hidden.bs.dropdown":function(t){e.$element.trigger("hidden"+z,t)},"show.bs.dropdown":function(t){e.$element.trigger("show"+z,t)},"shown.bs.dropdown":function(t){e.$element.trigger("shown"+z,t)}}),n.hasAttribute("required")&&this.$element.on("invalid"+z,(function(){e.$button[0].classList.add("bs-invalid"),e.$element.on("shown"+z+".invalid",(function(){e.$element.val(e.$element.val()).off("shown"+z+".invalid")})).on("rendered"+z,(function(){this.validity.valid&&e.$button[0].classList.remove("bs-invalid"),e.$element.off("rendered"+z)})),e.$button.on("blur"+z,(function(){e.$element.trigger("focus").trigger("blur"),e.$button.off("blur"+z)}))})),setTimeout((function(){e.buildList(),e.$element.trigger("loaded"+z)}))},createDropdown:function(){var t=this.multiple||this.options.showTick?" show-tick":"",n=this.multiple?' aria-multiselectable="true"':"",r="",i=this.autofocus?" autofocus":"";F.major<4&&this.$element.parent().hasClass("input-group")&&(r=" input-group-btn");var o,a="",s="",l="",c="";return this.options.header&&(a='
'+this.options.header+"
"),this.options.liveSearch&&(s=''),this.multiple&&this.options.actionsBox&&(l='
"),this.multiple&&this.options.doneButton&&(c='
"),o='",e(o)},setPositionData:function(){this.selectpicker.view.canHighlight=[],this.selectpicker.view.size=0,this.selectpicker.view.firstHighlightIndex=!1;for(var e=0;e=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(t,n,r){var i,o,s=this,l=0,c=[];if(this.selectpicker.isSearching=t,this.selectpicker.current=t?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),n)if(r)l=this.$menuInner[0].scrollTop;else if(!s.multiple){var u=s.$element[0],d=(u.options[u.selectedIndex]||{}).liIndex;if("number"==typeof d&&!1!==s.options.size){var p=s.selectpicker.main.data[d],h=p&&p.position;h&&(l=h-(s.sizeInfo.menuInnerHeight+s.sizeInfo.liHeight)/2)}}function f(e,n){var r,l,u,d,p,h,f,m,g,v,b=s.selectpicker.current.elements.length,y=[],_=!0,w=s.isVirtual();s.selectpicker.view.scrollTop=e,r=Math.ceil(s.sizeInfo.menuInnerHeight/s.sizeInfo.liHeight*1.5),l=Math.round(b/r)||1;for(var x=0;xb-1?0:s.selectpicker.current.data[b-1].position-s.selectpicker.current.data[s.selectpicker.view.position1-1].position,E.firstChild.style.marginTop=k+"px",E.firstChild.style.marginBottom=S+"px"):(E.firstChild.style.marginTop=0,E.firstChild.style.marginBottom=0),E.firstChild.appendChild(C),!0===w&&s.sizeInfo.hasScrollBar){var L=E.firstChild.offsetWidth;if(n&&Ls.sizeInfo.selectWidth)E.firstChild.style.minWidth=s.sizeInfo.menuInnerInnerWidth+"px";else if(L>s.sizeInfo.menuInnerInnerWidth){s.$menu[0].style.minWidth=0;var M=E.firstChild.offsetWidth;M>s.sizeInfo.menuInnerInnerWidth&&(s.sizeInfo.menuInnerInnerWidth=M,E.firstChild.style.minWidth=s.sizeInfo.menuInnerInnerWidth+"px"),s.$menu[0].style.minWidth=""}}}if(s.prevActiveIndex=s.activeIndex,s.options.liveSearch){if(t&&n){var R,U=0;s.selectpicker.view.canHighlight[U]||(U=1+s.selectpicker.view.canHighlight.slice(1).indexOf(!0)),R=s.selectpicker.view.visibleElements[U],s.defocusItem(s.selectpicker.view.currentActive),s.activeIndex=(s.selectpicker.current.data[U]||{}).index,s.focusItem(R)}}else s.$menuInner.trigger("focus")}f(l,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",(function(e,t){s.noScroll||f(this.scrollTop,t),s.noScroll=!1})),e(window).off("resize"+z+"."+this.selectId+".createView").on("resize"+z+"."+this.selectId+".createView",(function(){s.$newElement.hasClass(q.SHOW)&&f(s.$menuInner[0].scrollTop)}))},focusItem:function(e,t,n){if(e){t=t||this.selectpicker.main.data[this.activeIndex];var r=e.firstChild;r&&(r.setAttribute("aria-setsize",this.selectpicker.view.size),r.setAttribute("aria-posinset",t.posinset),!0!==n&&(this.focusedParent.setAttribute("aria-activedescendant",r.id),e.classList.add("active"),r.classList.add("active")))}},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e=this,t=!1;if(this.options.title&&!this.multiple){this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),t=!0;var n=this.$element[0],r=!1,i=!this.selectpicker.view.titleOption.parentNode,o=n.selectedIndex,a=n.options[o],s=window.performance&&window.performance.getEntriesByType("navigation"),l=s&&s.length?"back_forward"!==s[0].type:2!==window.performance.navigation.type;i&&(this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",r=!a||0===o&&!1===a.defaultSelected&&void 0===this.$element.data("selected")),(i||0!==this.selectpicker.view.titleOption.index)&&n.insertBefore(this.selectpicker.view.titleOption,n.firstChild),r&&l?n.selectedIndex=0:"complete"!==document.readyState&&window.addEventListener("pageshow",(function(){e.selectpicker.view.displayedValue!==n.value&&e.render()}))}return t},buildData:function(){var e=':not([hidden]):not([data-hidden="true"])',t=[],n=0,r=this.setPlaceholder()?1:0;this.options.hideDisabled&&(e+=":not(:disabled)");var i=this.$element[0].querySelectorAll("select > *"+e);function o(e){var n=t[t.length-1];n&&"divider"===n.type&&(n.optID||e.optID)||((e=e||{}).type="divider",t.push(e))}function a(e,n){if((n=n||{}).divider="true"===e.getAttribute("data-divider"),n.divider)o({optID:n.optID});else{var r=t.length,i=e.style.cssText,a=i?O(i):"",s=(e.className||"")+(n.optgroupClass||"");n.optID&&(s="opt "+s),n.optionClass=s.trim(),n.inlineStyle=a,n.text=e.textContent,n.content=e.getAttribute("data-content"),n.tokens=e.getAttribute("data-tokens"),n.subtext=e.getAttribute("data-subtext"),n.icon=e.getAttribute("data-icon"),e.liIndex=r,n.display=n.content||n.text,n.type="option",n.index=r,n.option=e,n.selected=!!e.selected,n.disabled=n.disabled||!!e.disabled,t.push(n)}}function s(i,s){var l=s[i],c=!(i-1r&&(r=o,e.selectpicker.view.widestOption=n[n.length-1])}!e.options.showTick&&!e.multiple||$.checkMark.parentNode||($.checkMark.className=this.options.iconBase+" "+e.options.tickIcon+" check-mark",$.a.appendChild($.checkMark));for(var o=t.length,a=0;a li")},render:function(){var e,t,n=this,r=this.$element[0],i=this.setPlaceholder()&&0===r.selectedIndex,o=f(r,this.options.hideDisabled),s=o.length,l=this.$button[0],c=l.querySelector(".filter-option-inner-inner"),u=document.createTextNode(this.options.multipleSeparator),d=$.fragment.cloneNode(!1),p=!1;if(l.classList.toggle("bs-placeholder",n.multiple?!s:!m(r,o)),n.multiple||1!==o.length||(n.selectpicker.view.displayedValue=m(r,o)),"static"===this.options.selectedTextFormat)d=G.text.call(this,{text:this.options.title},!0);else if((e=this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&s>1)&&(e=(t=this.options.selectedTextFormat.split(">")).length>1&&s>t[1]||1===t.length&&s>=2),!1===e){if(!i){for(var h=0;h0&&d.appendChild(u.cloneNode(!1)),g.title?b.text=g.title:v&&(v.content&&n.options.showContent?(b.content=v.content.toString(),p=!0):(n.options.showIcon&&(b.icon=v.icon),n.options.showSubtext&&!n.multiple&&v.subtext&&(b.subtext=" "+v.subtext),b.text=g.textContent.trim())),d.appendChild(G.text.call(this,b,!0))}s>49&&d.appendChild(document.createTextNode("..."))}}else{var y=':not([hidden]):not([data-hidden="true"]):not([data-divider="true"])';this.options.hideDisabled&&(y+=":not(:disabled)");var _=this.$element[0].querySelectorAll("select > option"+y+", optgroup"+y+" option"+y).length,w="function"==typeof this.options.countSelectedText?this.options.countSelectedText(s,_):this.options.countSelectedText;d=G.text.call(this,{text:w.replace("{0}",s.toString()).replace("{1}",_.toString())},!0)}if(null==this.options.title&&(this.options.title=this.$element.attr("title")),d.childNodes.length||(d=G.text.call(this,{text:void 0!==this.options.title?this.options.title:this.options.noneSelectedText},!0)),l.title=d.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&p&&a([d],n.options.whiteList,n.options.sanitizeFn),c.innerHTML="",c.appendChild(d),F.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")){var x=l.querySelector(".filter-expand"),T=c.cloneNode(!0);T.className="filter-expand",x?l.replaceChild(T,x):l.appendChild(T)}this.$element.trigger("rendered"+z)},setStyle:function(e,t){var n,r=this.$button[0],i=this.$newElement[0],o=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),F.major<4&&(i.classList.add("bs3"),i.parentNode.classList&&i.parentNode.classList.contains("input-group")&&(i.previousElementSibling||i.nextElementSibling)&&(i.previousElementSibling||i.nextElementSibling).classList.contains("input-group-addon")&&i.classList.add("bs3-has-addon")),n=e?e.trim():o,"add"==t?n&&r.classList.add.apply(r.classList,n.split(" ")):"remove"==t?n&&r.classList.remove.apply(r.classList,n.split(" ")):(o&&r.classList.remove.apply(r.classList,o.split(" ")),n&&r.classList.add.apply(r.classList,n.split(" ")))},liHeight:function(t){if(t||!1!==this.options.size&&!Object.keys(this.sizeInfo).length){var n,r=$.div.cloneNode(!1),i=$.div.cloneNode(!1),o=$.div.cloneNode(!1),a=document.createElement("ul"),s=$.li.cloneNode(!1),l=$.li.cloneNode(!1),c=$.a.cloneNode(!1),u=$.span.cloneNode(!1),d=this.options.header&&this.$menu.find("."+q.POPOVERHEADER).length>0?this.$menu.find("."+q.POPOVERHEADER)[0].cloneNode(!0):null,p=this.options.liveSearch?$.div.cloneNode(!1):null,h=this.options.actionsBox&&this.multiple&&this.$menu.find(".bs-actionsbox").length>0?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,f=this.options.doneButton&&this.multiple&&this.$menu.find(".bs-donebutton").length>0?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null,m=this.$element.find("option")[0];if(this.sizeInfo.selectWidth=this.$newElement[0].offsetWidth,u.className="text",c.className="dropdown-item "+(m?m.className:""),r.className=this.$menu[0].parentNode.className+" "+q.SHOW,r.style.width=0,"auto"===this.options.width&&(i.style.minWidth=0),i.className=q.MENU+" "+q.SHOW,o.className="inner "+q.SHOW,a.className=q.MENU+" inner "+("4"===F.major?q.SHOW:""),s.className=q.DIVIDER,l.className="dropdown-header",u.appendChild(document.createTextNode("​")),this.selectpicker.current.data.length)for(var g=0;gthis.sizeInfo.menuExtras.vert&&s+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot,!0===this.selectpicker.isSearching&&(l=this.selectpicker.dropup),this.$newElement.toggleClass(q.DROPUP,l),this.selectpicker.dropup=l),"auto"===this.options.size)i=this.selectpicker.current.elements.length>3?3*this.sizeInfo.liHeight+this.sizeInfo.menuExtras.vert-2:0,n=this.sizeInfo.selectOffsetBot-this.sizeInfo.menuExtras.vert,r=i+d+p+h+f,a=Math.max(i-g.vert,0),this.$newElement.hasClass(q.DROPUP)&&(n=this.sizeInfo.selectOffsetTop-this.sizeInfo.menuExtras.vert),o=n,t=n-d-p-h-f-g.vert;else if(this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size){for(var b=0;bthis.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth),"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(q.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRightthis.options.size&&r.off("resize"+z+"."+this.selectId+".setMenuSize scroll"+z+"."+this.selectId+".setMenuSize")}this.createView(!1,!0,t)},setWidth:function(){var e=this;"auto"===this.options.width?requestAnimationFrame((function(){e.$menu.css("min-width","0"),e.$element.on("loaded"+z,(function(){e.liHeight(),e.setMenuSize();var t=e.$newElement.clone().appendTo("body"),n=t.css("width","auto").children("button").outerWidth();t.remove(),e.sizeInfo.selectWidth=Math.max(e.sizeInfo.totalMenuWidth,n),e.$newElement.css("width",e.sizeInfo.selectWidth+"px")}))})):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=e('
');var t,n,r,i=this,o=e(this.options.container),a=function(a){var s={},l=i.options.display||!!e.fn.dropdown.Constructor.Default&&e.fn.dropdown.Constructor.Default.display;i.$bsContainer.addClass(a.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(q.DROPUP,a.hasClass(q.DROPUP)),t=a.offset(),o.is("body")?n={top:0,left:0}:((n=o.offset()).top+=parseInt(o.css("borderTopWidth"))-o.scrollTop(),n.left+=parseInt(o.css("borderLeftWidth"))-o.scrollLeft()),r=a.hasClass(q.DROPUP)?0:a[0].offsetHeight,(F.major<4||"static"===l)&&(s.top=t.top-n.top+r,s.left=t.left-n.left),s.width=a[0].offsetWidth,i.$bsContainer.css(s)};this.$button.on("click.bs.dropdown.data-api",(function(){i.isDisabled()||(a(i.$newElement),i.$bsContainer.appendTo(i.options.container).toggleClass(q.SHOW,!i.$button.hasClass(q.SHOW)).append(i.$menu))})),e(window).off("resize"+z+"."+this.selectId+" scroll"+z+"."+this.selectId).on("resize"+z+"."+this.selectId+" scroll"+z+"."+this.selectId,(function(){i.$newElement.hasClass(q.SHOW)&&a(i.$newElement)})),this.$element.on("hide"+z,(function(){i.$menu.data("height",i.$menu.height()),i.$bsContainer.detach()}))},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length)for(var n=0;n3&&!t.dropdown&&(t.dropdown=t.$button.data("bs.dropdown"),t.dropdown._menu=t.$menu[0])})),this.$button.on("click.bs.dropdown.data-api",(function(){t.$newElement.hasClass(q.SHOW)||t.setSize()})),this.$element.on("shown"+z,(function(){t.$menuInner[0].scrollTop!==t.selectpicker.view.scrollTop&&(t.$menuInner[0].scrollTop=t.selectpicker.view.scrollTop),F.major>3?requestAnimationFrame(i):r()})),this.$menuInner.on("mouseenter","li a",(function(e){var n=this.parentElement,r=t.isVirtual()?t.selectpicker.view.position0:0,i=Array.prototype.indexOf.call(n.parentElement.children,n),o=t.selectpicker.current.data[i+r];t.focusItem(n,o,!0)})),this.$menuInner.on("click","li a",(function(n,r){var i=e(this),o=t.$element[0],a=t.isVirtual()?t.selectpicker.view.position0:0,s=t.selectpicker.current.data[i.parent().index()+a],l=s.index,c=m(o),u=o.selectedIndex,d=o.options[u],p=!0;if(t.multiple&&1!==t.options.maxOptions&&n.stopPropagation(),n.preventDefault(),!t.isDisabled()&&!i.parent().hasClass(q.DISABLED)){var h=s.option,g=e(h),b=h.selected,y=g.parent("optgroup"),_=y.find("option"),w=t.options.maxOptions,x=y.data("maxOptions")||!1;if(l===t.activeIndex&&(r=!0),r||(t.prevActiveIndex=t.activeIndex,t.activeIndex=void 0),t.multiple){if(h.selected=!b,t.setSelected(l,!b),t.focusedParent.focus(),!1!==w||!1!==x){var T=w
');A[2]&&(I=I.replace("{var}",A[2][w>1?0:1]),j=j.replace("{var}",A[2][x>1?0:1])),h.selected=!1,t.$menu.append(O),w&&T&&(O.append(e("
"+I+"
")),p=!1,t.$element.trigger("maxReached"+z)),x&&k&&(O.append(e("
"+j+"
")),p=!1,t.$element.trigger("maxReachedGrp"+z)),setTimeout((function(){t.setSelected(l,!1)}),10),O[0].classList.add("fadeOut"),setTimeout((function(){O.remove()}),1050)}}}else d&&(d.selected=!1),h.selected=!0,t.setSelected(l,!0);!t.multiple||t.multiple&&1===t.options.maxOptions?t.$button.trigger("focus"):t.options.liveSearch&&t.$searchbox.trigger("focus"),p&&(t.multiple||u!==o.selectedIndex)&&(v=[h.index,g.prop("selected"),c],t.$element.triggerNative("change"))}})),this.$menu.on("click","li."+q.DISABLED+" a, ."+q.POPOVERHEADER+", ."+q.POPOVERHEADER+" :not(.close)",(function(n){n.currentTarget==this&&(n.preventDefault(),n.stopPropagation(),t.options.liveSearch&&!e(n.target).hasClass("close")?t.$searchbox.trigger("focus"):t.$button.trigger("focus"))})),this.$menuInner.on("click",".divider, .dropdown-header",(function(e){e.preventDefault(),e.stopPropagation(),t.options.liveSearch?t.$searchbox.trigger("focus"):t.$button.trigger("focus")})),this.$menu.on("click","."+q.POPOVERHEADER+" .close",(function(){t.$button.trigger("click")})),this.$searchbox.on("click",(function(e){e.stopPropagation()})),this.$menu.on("click",".actions-btn",(function(n){t.options.liveSearch?t.$searchbox.trigger("focus"):t.$button.trigger("focus"),n.preventDefault(),n.stopPropagation(),e(this).hasClass("bs-select-all")?t.selectAll():t.deselectAll()})),this.$button.on("focus"+z,(function(e){var n=t.$element[0].getAttribute("tabindex");void 0!==n&&e.originalEvent&&e.originalEvent.isTrusted&&(this.setAttribute("tabindex",n),t.$element[0].setAttribute("tabindex",-1),t.selectpicker.view.tabindex=n)})).on("blur"+z,(function(e){void 0!==t.selectpicker.view.tabindex&&e.originalEvent&&e.originalEvent.isTrusted&&(t.$element[0].setAttribute("tabindex",t.selectpicker.view.tabindex),this.setAttribute("tabindex",-1),t.selectpicker.view.tabindex=void 0)})),this.$element.on("change"+z,(function(){t.render(),t.$element.trigger("changed"+z,v),v=null})).on("focus"+z,(function(){t.options.mobile||t.$button[0].focus()}))},liveSearchListener:function(){var e=this;this.$button.on("click.bs.dropdown.data-api",(function(){e.$searchbox.val()&&(e.$searchbox.val(""),e.selectpicker.search.previousValue=void 0)})),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",(function(e){e.stopPropagation()})),this.$searchbox.on("input propertychange",(function(){var t=e.$searchbox[0].value;if(e.selectpicker.search.elements=[],e.selectpicker.search.data=[],t){var n=[],r=t.toUpperCase(),i={},o=[],a=e._searchStyle(),s=e.options.liveSearchNormalize;s&&(r=S(r));for(var l=0;l0&&(i[c.headerIndex-1]=!0,o.push(c.headerIndex-1)),i[c.headerIndex]=!0,o.push(c.headerIndex),i[c.lastIndex+1]=!0),i[l]&&"optgroup-label"!==c.type&&o.push(l)}l=0;for(var u=o.length;l=112&&t.which<=123))if(!(r=c.$newElement.hasClass(q.SHOW))&&(h||t.which>=48&&t.which<=57||t.which>=96&&t.which<=105||t.which>=65&&t.which<=90)&&(c.$button.trigger("click.bs.dropdown.data-api"),c.options.liveSearch))c.$searchbox.trigger("focus");else{if(t.which===D&&r&&(t.preventDefault(),c.$button.trigger("click.bs.dropdown.data-api").trigger("focus")),h){if(!u.length)return;-1!==(n=(i=c.selectpicker.main.elements[c.activeIndex])?Array.prototype.indexOf.call(i.parentElement.children,i):-1)&&c.defocusItem(i),t.which===R?(-1!==n&&n--,n+m<0&&(n+=u.length),c.selectpicker.view.canHighlight[n+m]||-1==(n=c.selectpicker.view.canHighlight.slice(0,n+m).lastIndexOf(!0)-m)&&(n=u.length-1)):(t.which===U||p)&&(++n+m>=c.selectpicker.view.canHighlight.length&&(n=c.selectpicker.view.firstHighlightIndex),c.selectpicker.view.canHighlight[n+m]||(n=n+1+c.selectpicker.view.canHighlight.slice(n+m+1).indexOf(!0))),t.preventDefault();var g=m+n;t.which===R?0===m&&n===u.length-1?(c.$menuInner[0].scrollTop=c.$menuInner[0].scrollHeight,g=c.selectpicker.current.elements.length-1):d=(a=(o=c.selectpicker.current.data[g]).position-o.height)f),i=c.selectpicker.current.elements[g],c.activeIndex=c.selectpicker.current.data[g].index,c.focusItem(i),c.selectpicker.view.currentActive=i,d&&(c.$menuInner[0].scrollTop=a),c.options.liveSearch?c.$searchbox.trigger("focus"):s.trigger("focus")}else if(!s.is("input")&&!W.test(t.which)||t.which===L&&c.selectpicker.keydown.keyHistory){var v,b,_=[];t.preventDefault(),c.selectpicker.keydown.keyHistory+=P[t.which],c.selectpicker.keydown.resetKeyHistory.cancel&&clearTimeout(c.selectpicker.keydown.resetKeyHistory.cancel),c.selectpicker.keydown.resetKeyHistory.cancel=c.selectpicker.keydown.resetKeyHistory.start(),b=c.selectpicker.keydown.keyHistory,/^(.)\1+$/.test(b)&&(b=b.charAt(0));for(var w=0;w0?(a=o.position-o.height,d=!0):(a=o.position-c.sizeInfo.menuInnerHeight,d=o.position>f+c.sizeInfo.menuInnerHeight),i=c.selectpicker.main.elements[v],c.activeIndex=_[T],c.focusItem(i),i&&i.firstChild.focus(),d&&(c.$menuInner[0].scrollTop=a),s.trigger("focus")}}r&&(t.which===L&&!c.selectpicker.keydown.keyHistory||t.which===N||t.which===M&&c.options.selectOnTab)&&(t.which!==L&&t.preventDefault(),c.options.liveSearch&&t.which===L||(c.$menuInner.find(".active a").trigger("click",!0),s.trigger("focus"),c.options.liveSearch||(t.preventDefault(),e(document).data("spaceSelect",!0))))}},mobile:function(){this.options.mobile=!0,this.$element[0].classList.add("mobile-device")},refresh:function(){var t=e.extend({},this.options,this.$element.data());this.options=t,this.checkDisabled(),this.buildData(),this.setStyle(),this.render(),this.buildList(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+z)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),this.$bsContainer?this.$bsContainer.remove():this.$menu.remove(),this.selectpicker.view.titleOption&&this.selectpicker.view.titleOption.parentNode&&this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption),this.$element.off(z).removeData("selectpicker").removeClass("bs-select-hidden selectpicker"),e(window).off(z+"."+this.selectId)}};var Q=e.fn.selectpicker;function J(){if(e.fn.dropdown)return(e.fn.dropdown.Constructor._dataApiKeydownHandler||e.fn.dropdown.Constructor.prototype.keydown).apply(this,arguments)}e.fn.selectpicker=Y,e.fn.selectpicker.Constructor=K,e.fn.selectpicker.noConflict=function(){return e.fn.selectpicker=Q,this},e(document).off("keydown.bs.dropdown.data-api").on("keydown.bs.dropdown.data-api",':not(.bootstrap-select) > [data-toggle="dropdown"]',J).on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > .dropdown-menu",J).on("keydown"+z,'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',K.prototype.keydown).on("focusin.modal",'.bootstrap-select [data-toggle="dropdown"], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',(function(e){e.stopPropagation()})),e(window).on("load"+z+".data-api",(function(){e(".selectpicker").each((function(){var t=e(this);Y.call(t,t.data())}))}))}(e)}.apply(t,r),void 0===i||(e.exports=i)},2754:function(e,t,n){!function(e,t,n){"use strict";function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=r(t),o=r(n);function a(e,t){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};p.jQueryDetection(),i.default.fn.emulateTransitionEnd=d,i.default.event.special[p.TRANSITION_END]={bindType:u,delegateType:u,handle:function(e){if(i.default(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}};var h="alert",f="bs.alert",m="."+f,g=i.default.fn[h],v="close"+m,b="closed"+m,y="click"+m+".data-api",_=function(){function e(e){this._element=e}var t=e.prototype;return t.close=function(e){var t=this._element;e&&(t=this._getRootElement(e)),this._triggerCloseEvent(t).isDefaultPrevented()||this._removeElement(t)},t.dispose=function(){i.default.removeData(this._element,f),this._element=null},t._getRootElement=function(e){var t=p.getSelectorFromElement(e),n=!1;return t&&(n=document.querySelector(t)),n||(n=i.default(e).closest(".alert")[0]),n},t._triggerCloseEvent=function(e){var t=i.default.Event(v);return i.default(e).trigger(t),t},t._removeElement=function(e){var t=this;if(i.default(e).removeClass("show"),i.default(e).hasClass("fade")){var n=p.getTransitionDurationFromElement(e);i.default(e).one(p.TRANSITION_END,(function(n){return t._destroyElement(e,n)})).emulateTransitionEnd(n)}else this._destroyElement(e)},t._destroyElement=function(e){i.default(e).detach().trigger(b).remove()},e._jQueryInterface=function(t){return this.each((function(){var n=i.default(this),r=n.data(f);r||(r=new e(this),n.data(f,r)),"close"===t&&r[t](this)}))},e._handleDismiss=function(e){return function(t){t&&t.preventDefault(),e.close(this)}},s(e,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),e}();i.default(document).on(y,'[data-dismiss="alert"]',_._handleDismiss(new _)),i.default.fn[h]=_._jQueryInterface,i.default.fn[h].Constructor=_,i.default.fn[h].noConflict=function(){return i.default.fn[h]=g,_._jQueryInterface};var w="button",x="bs.button",T="."+x,k=".data-api",S=i.default.fn[w],E="active",C="click"+T+k,A="focus"+T+k+" blur"+T+k,I="load"+T+k,j='[data-toggle^="button"]',O='input:not([type="hidden"])',P=".btn",D=function(){function e(e){this._element=e,this.shouldAvoidTriggerChange=!1}var t=e.prototype;return t.toggle=function(){var e=!0,t=!0,n=i.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var r=this._element.querySelector(O);if(r){if("radio"===r.type)if(r.checked&&this._element.classList.contains(E))e=!1;else{var o=n.querySelector(".active");o&&i.default(o).removeClass(E)}e&&("checkbox"!==r.type&&"radio"!==r.type||(r.checked=!this._element.classList.contains(E)),this.shouldAvoidTriggerChange||i.default(r).trigger("change")),r.focus(),t=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(t&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(E)),e&&i.default(this._element).toggleClass(E))},t.dispose=function(){i.default.removeData(this._element,x),this._element=null},e._jQueryInterface=function(t,n){return this.each((function(){var r=i.default(this),o=r.data(x);o||(o=new e(this),r.data(x,o)),o.shouldAvoidTriggerChange=n,"toggle"===t&&o[t]()}))},s(e,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),e}();i.default(document).on(C,j,(function(e){var t=e.target,n=t;if(i.default(t).hasClass("btn")||(t=i.default(t).closest(P)[0]),!t||t.hasAttribute("disabled")||t.classList.contains("disabled"))e.preventDefault();else{var r=t.querySelector(O);if(r&&(r.hasAttribute("disabled")||r.classList.contains("disabled")))return void e.preventDefault();"INPUT"!==n.tagName&&"LABEL"===t.tagName||D._jQueryInterface.call(i.default(t),"toggle","INPUT"===n.tagName)}})).on(A,j,(function(e){var t=i.default(e.target).closest(P)[0];i.default(t).toggleClass("focus",/^focus(in)?$/.test(e.type))})),i.default(window).on(I,(function(){for(var e=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),t=0,n=e.length;t0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var t=e.prototype;return t.next=function(){this._isSliding||this._slide(B)},t.nextWhenVisible=function(){var e=i.default(this._element);!document.hidden&&e.is(":visible")&&"hidden"!==e.css("visibility")&&this.next()},t.prev=function(){this._isSliding||this._slide(z)},t.pause=function(e){e||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(p.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},t.cycle=function(e){e||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},t.to=function(e){var t=this;this._activeElement=this._element.querySelector(te);var n=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)i.default(this._element).one(H,(function(){return t.to(e)}));else{if(n===e)return this.pause(),void this.cycle();var r=e>n?B:z;this._slide(r,this._items[e])}},t.dispose=function(){i.default(this._element).off(M),i.default.removeData(this._element,L),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},t._getConfig=function(e){return e=l({},ne,e),p.typeCheckConfig(N,e,re),e},t._handleSwipe=function(){var e=Math.abs(this.touchDeltaX);if(!(e<=40)){var t=e/this.touchDeltaX;this.touchDeltaX=0,t>0&&this.prev(),t<0&&this.next()}},t._addEventListeners=function(){var e=this;this._config.keyboard&&i.default(this._element).on($,(function(t){return e._keydown(t)})),"hover"===this._config.pause&&i.default(this._element).on(V,(function(t){return e.pause(t)})).on(W,(function(t){return e.cycle(t)})),this._config.touch&&this._addTouchEventListeners()},t._addTouchEventListeners=function(){var e=this;if(this._touchSupported){var t=function(t){e._pointerEvent&&ie[t.originalEvent.pointerType.toUpperCase()]?e.touchStartX=t.originalEvent.clientX:e._pointerEvent||(e.touchStartX=t.originalEvent.touches[0].clientX)},n=function(t){e._pointerEvent&&ie[t.originalEvent.pointerType.toUpperCase()]&&(e.touchDeltaX=t.originalEvent.clientX-e.touchStartX),e._handleSwipe(),"hover"===e._config.pause&&(e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout((function(t){return e.cycle(t)}),500+e._config.interval))};i.default(this._element.querySelectorAll(".carousel-item img")).on(J,(function(e){return e.preventDefault()})),this._pointerEvent?(i.default(this._element).on(Y,(function(e){return t(e)})),i.default(this._element).on(Q,(function(e){return n(e)})),this._element.classList.add("pointer-event")):(i.default(this._element).on(G,(function(e){return t(e)})),i.default(this._element).on(X,(function(t){return function(t){e.touchDeltaX=t.originalEvent.touches&&t.originalEvent.touches.length>1?0:t.originalEvent.touches[0].clientX-e.touchStartX}(t)})),i.default(this._element).on(K,(function(e){return n(e)})))}},t._keydown=function(e){if(!/input|textarea/i.test(e.target.tagName))switch(e.which){case 37:e.preventDefault(),this.prev();break;case 39:e.preventDefault(),this.next()}},t._getItemIndex=function(e){return this._items=e&&e.parentNode?[].slice.call(e.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(e)},t._getItemByDirection=function(e,t){var n=e===B,r=e===z,i=this._getItemIndex(t),o=this._items.length-1;if((r&&0===i||n&&i===o)&&!this._config.wrap)return t;var a=(i+(e===z?-1:1))%this._items.length;return-1===a?this._items[this._items.length-1]:this._items[a]},t._triggerSlideEvent=function(e,t){var n=this._getItemIndex(e),r=this._getItemIndex(this._element.querySelector(te)),o=i.default.Event(q,{relatedTarget:e,direction:t,from:r,to:n});return i.default(this._element).trigger(o),o},t._setActiveIndicatorElement=function(e){if(this._indicatorsElement){var t=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));i.default(t).removeClass(F);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&i.default(n).addClass(F)}},t._updateInterval=function(){var e=this._activeElement||this._element.querySelector(te);if(e){var t=parseInt(e.getAttribute("data-interval"),10);t?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=t):this._config.interval=this._config.defaultInterval||this._config.interval}},t._slide=function(e,t){var n,r,o,a=this,s=this._element.querySelector(te),l=this._getItemIndex(s),c=t||s&&this._getItemByDirection(e,s),u=this._getItemIndex(c),d=Boolean(this._interval);if(e===B?(n="carousel-item-left",r="carousel-item-next",o="left"):(n="carousel-item-right",r="carousel-item-prev",o="right"),c&&i.default(c).hasClass(F))this._isSliding=!1;else if(!this._triggerSlideEvent(c,o).isDefaultPrevented()&&s&&c){this._isSliding=!0,d&&this.pause(),this._setActiveIndicatorElement(c),this._activeElement=c;var h=i.default.Event(H,{relatedTarget:c,direction:o,from:l,to:u});if(i.default(this._element).hasClass("slide")){i.default(c).addClass(r),p.reflow(c),i.default(s).addClass(n),i.default(c).addClass(n);var f=p.getTransitionDurationFromElement(s);i.default(s).one(p.TRANSITION_END,(function(){i.default(c).removeClass(n+" "+r).addClass(F),i.default(s).removeClass(F+" "+r+" "+n),a._isSliding=!1,setTimeout((function(){return i.default(a._element).trigger(h)}),0)})).emulateTransitionEnd(f)}else i.default(s).removeClass(F),i.default(c).addClass(F),this._isSliding=!1,i.default(this._element).trigger(h);d&&this.cycle()}},e._jQueryInterface=function(t){return this.each((function(){var n=i.default(this).data(L),r=l({},ne,i.default(this).data());"object"==typeof t&&(r=l({},r,t));var o="string"==typeof t?t:r.slide;if(n||(n=new e(this,r),i.default(this).data(L,n)),"number"==typeof t)n.to(t);else if("string"==typeof o){if(void 0===n[o])throw new TypeError('No method named "'+o+'"');n[o]()}else r.interval&&r.ride&&(n.pause(),n.cycle())}))},e._dataApiClickHandler=function(t){var n=p.getSelectorFromElement(this);if(n){var r=i.default(n)[0];if(r&&i.default(r).hasClass("carousel")){var o=l({},i.default(r).data(),i.default(this).data()),a=this.getAttribute("data-slide-to");a&&(o.interval=!1),e._jQueryInterface.call(i.default(r),o),a&&i.default(r).data(L).to(a),t.preventDefault()}}},s(e,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return ne}}]),e}();i.default(document).on(ee,"[data-slide], [data-slide-to]",oe._dataApiClickHandler),i.default(window).on(Z,(function(){for(var e=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),t=0,n=e.length;t0&&(this._selector=a,this._triggerArray.push(o))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var t=e.prototype;return t.toggle=function(){i.default(this._element).hasClass(ue)?this.hide():this.show()},t.show=function(){var t,n,r=this;if(!(this._isTransitioning||i.default(this._element).hasClass(ue)||(this._parent&&0===(t=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(e){return"string"==typeof r._config.parent?e.getAttribute("data-parent")===r._config.parent:e.classList.contains(de)}))).length&&(t=null),t&&(n=i.default(t).not(this._selector).data(se))&&n._isTransitioning))){var o=i.default.Event(me);if(i.default(this._element).trigger(o),!o.isDefaultPrevented()){t&&(e._jQueryInterface.call(i.default(t).not(this._selector),"hide"),n||i.default(t).data(se,null));var a=this._getDimension();i.default(this._element).removeClass(de).addClass(pe),this._element.style[a]=0,this._triggerArray.length&&i.default(this._triggerArray).removeClass(he).attr("aria-expanded",!0),this.setTransitioning(!0);var s="scroll"+(a[0].toUpperCase()+a.slice(1)),l=p.getTransitionDurationFromElement(this._element);i.default(this._element).one(p.TRANSITION_END,(function(){i.default(r._element).removeClass(pe).addClass(de+" "+ue),r._element.style[a]="",r.setTransitioning(!1),i.default(r._element).trigger(ge)})).emulateTransitionEnd(l),this._element.style[a]=this._element[s]+"px"}}},t.hide=function(){var e=this;if(!this._isTransitioning&&i.default(this._element).hasClass(ue)){var t=i.default.Event(ve);if(i.default(this._element).trigger(t),!t.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",p.reflow(this._element),i.default(this._element).addClass(pe).removeClass(de+" "+ue);var r=this._triggerArray.length;if(r>0)for(var o=0;o0},t._getOffset=function(){var e=this,t={};return"function"==typeof this._config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e._config.offset(t.offsets,e._element)),t}:t.offset=this._config.offset,t},t._getPopperConfig=function(){var e={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(e.modifiers.applyStyle={enabled:!1}),l({},e,this._config.popperConfig)},e._jQueryInterface=function(t){return this.each((function(){var n=i.default(this).data(Se);if(n||(n=new e(this,"object"==typeof t?t:null),i.default(this).data(Se,n)),"string"==typeof t){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},e._clearMenus=function(t){if(!t||3!==t.which&&("keyup"!==t.type||9===t.which))for(var n=[].slice.call(document.querySelectorAll(ze)),r=0,o=n.length;r0&&a--,40===t.which&&adocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(Ze);var r=p.getTransitionDurationFromElement(this._dialog);i.default(this._element).off(p.TRANSITION_END),i.default(this._element).one(p.TRANSITION_END,(function(){e._element.classList.remove(Ze),n||i.default(e._element).one(p.TRANSITION_END,(function(){e._element.style.overflowY=""})).emulateTransitionEnd(e._element,r)})).emulateTransitionEnd(r),this._element.focus()}},t._showElement=function(e){var t=this,n=i.default(this._element).hasClass(Qe),r=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),i.default(this._dialog).hasClass("modal-dialog-scrollable")&&r?r.scrollTop=0:this._element.scrollTop=0,n&&p.reflow(this._element),i.default(this._element).addClass(Je),this._config.focus&&this._enforceFocus();var o=i.default.Event(it,{relatedTarget:e}),a=function(){t._config.focus&&t._element.focus(),t._isTransitioning=!1,i.default(t._element).trigger(o)};if(n){var s=p.getTransitionDurationFromElement(this._dialog);i.default(this._dialog).one(p.TRANSITION_END,a).emulateTransitionEnd(s)}else a()},t._enforceFocus=function(){var e=this;i.default(document).off(ot).on(ot,(function(t){document!==t.target&&e._element!==t.target&&0===i.default(e._element).has(t.target).length&&e._element.focus()}))},t._setEscapeEvent=function(){var e=this;this._isShown?i.default(this._element).on(lt,(function(t){e._config.keyboard&&27===t.which?(t.preventDefault(),e.hide()):e._config.keyboard||27!==t.which||e._triggerBackdropTransition()})):this._isShown||i.default(this._element).off(lt)},t._setResizeEvent=function(){var e=this;this._isShown?i.default(window).on(at,(function(t){return e.handleUpdate(t)})):i.default(window).off(at)},t._hideModal=function(){var e=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){i.default(document.body).removeClass(Ye),e._resetAdjustments(),e._resetScrollbar(),i.default(e._element).trigger(nt)}))},t._removeBackdrop=function(){this._backdrop&&(i.default(this._backdrop).remove(),this._backdrop=null)},t._showBackdrop=function(e){var t=this,n=i.default(this._element).hasClass(Qe)?Qe:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),i.default(this._backdrop).appendTo(document.body),i.default(this._element).on(st,(function(e){t._ignoreBackdropClick?t._ignoreBackdropClick=!1:e.target===e.currentTarget&&("static"===t._config.backdrop?t._triggerBackdropTransition():t.hide())})),n&&p.reflow(this._backdrop),i.default(this._backdrop).addClass(Je),!e)return;if(!n)return void e();var r=p.getTransitionDurationFromElement(this._backdrop);i.default(this._backdrop).one(p.TRANSITION_END,e).emulateTransitionEnd(r)}else if(!this._isShown&&this._backdrop){i.default(this._backdrop).removeClass(Je);var o=function(){t._removeBackdrop(),e&&e()};if(i.default(this._element).hasClass(Qe)){var a=p.getTransitionDurationFromElement(this._backdrop);i.default(this._backdrop).one(p.TRANSITION_END,o).emulateTransitionEnd(a)}else o()}else e&&e()},t._adjustDialog=function(){var e=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&e&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!e&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var e=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(e.left+e.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Mt={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Rt={HIDE:"hide"+Tt,HIDDEN:"hidden"+Tt,SHOW:"show"+Tt,SHOWN:"shown"+Tt,INSERTED:"inserted"+Tt,CLICK:"click"+Tt,FOCUSIN:"focusin"+Tt,FOCUSOUT:"focusout"+Tt,MOUSEENTER:"mouseenter"+Tt,MOUSELEAVE:"mouseleave"+Tt},Ut=function(){function e(e,t){if(void 0===o.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=e,this.config=this._getConfig(t),this.tip=null,this._setListeners()}var t=e.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(e){if(this._isEnabled)if(e){var t=this.constructor.DATA_KEY,n=i.default(e.currentTarget).data(t);n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),i.default(e.currentTarget).data(t,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(i.default(this.getTipElement()).hasClass(It))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),i.default.removeData(this.element,this.constructor.DATA_KEY),i.default(this.element).off(this.constructor.EVENT_KEY),i.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&i.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===i.default(this.element).css("display"))throw new Error("Please use show on visible elements");var t=i.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){i.default(this.element).trigger(t);var n=p.findShadowRoot(this.element),r=i.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!r)return;var a=this.getTipElement(),s=p.getUID(this.constructor.NAME);a.setAttribute("id",s),this.element.setAttribute("aria-describedby",s),this.setContent(),this.config.animation&&i.default(a).addClass(At);var l="function"==typeof this.config.placement?this.config.placement.call(this,a,this.element):this.config.placement,c=this._getAttachment(l);this.addAttachmentClass(c);var u=this._getContainer();i.default(a).data(this.constructor.DATA_KEY,this),i.default.contains(this.element.ownerDocument.documentElement,this.tip)||i.default(a).appendTo(u),i.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new o.default(this.element,a,this._getPopperConfig(c)),i.default(a).addClass(It),i.default(a).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&i.default(document.body).children().on("mouseover",null,i.default.noop);var d=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,i.default(e.element).trigger(e.constructor.Event.SHOWN),t===Ot&&e._leave(null,e)};if(i.default(this.tip).hasClass(At)){var h=p.getTransitionDurationFromElement(this.tip);i.default(this.tip).one(p.TRANSITION_END,d).emulateTransitionEnd(h)}else d()}},t.hide=function(e){var t=this,n=this.getTipElement(),r=i.default.Event(this.constructor.Event.HIDE),o=function(){t._hoverState!==jt&&n.parentNode&&n.parentNode.removeChild(n),t._cleanTipClass(),t.element.removeAttribute("aria-describedby"),i.default(t.element).trigger(t.constructor.Event.HIDDEN),null!==t._popper&&t._popper.destroy(),e&&e()};if(i.default(this.element).trigger(r),!r.isDefaultPrevented()){if(i.default(n).removeClass(It),"ontouchstart"in document.documentElement&&i.default(document.body).children().off("mouseover",null,i.default.noop),this._activeTrigger.click=!1,this._activeTrigger[Dt]=!1,this._activeTrigger[Pt]=!1,i.default(this.tip).hasClass(At)){var a=p.getTransitionDurationFromElement(n);i.default(n).one(p.TRANSITION_END,o).emulateTransitionEnd(a)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(e){i.default(this.getTipElement()).addClass(St+"-"+e)},t.getTipElement=function(){return this.tip=this.tip||i.default(this.config.template)[0],this.tip},t.setContent=function(){var e=this.getTipElement();this.setElementContent(i.default(e.querySelectorAll(".tooltip-inner")),this.getTitle()),i.default(e).removeClass(At+" "+It)},t.setElementContent=function(e,t){"object"!=typeof t||!t.nodeType&&!t.jquery?this.config.html?(this.config.sanitize&&(t=_t(t,this.config.whiteList,this.config.sanitizeFn)),e.html(t)):e.text(t):this.config.html?i.default(t).parent().is(e)||e.empty().append(t):e.text(i.default(t).text())},t.getTitle=function(){var e=this.element.getAttribute("data-original-title");return e||(e="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),e},t._getPopperConfig=function(e){var t=this;return l({},{placement:e,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(e){e.originalPlacement!==e.placement&&t._handlePopperPlacementChange(e)},onUpdate:function(e){return t._handlePopperPlacementChange(e)}},this.config.popperConfig)},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:p.isElement(this.config.container)?i.default(this.config.container):i.default(document).find(this.config.container)},t._getAttachment=function(e){return Nt[e.toUpperCase()]},t._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach((function(t){if("click"===t)i.default(e.element).on(e.constructor.Event.CLICK,e.config.selector,(function(t){return e.toggle(t)}));else if("manual"!==t){var n=t===Pt?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,r=t===Pt?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;i.default(e.element).on(n,e.config.selector,(function(t){return e._enter(t)})).on(r,e.config.selector,(function(t){return e._leave(t)}))}})),this._hideModalHandler=function(){e.element&&e.hide()},i.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var e=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==e)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(e,t){var n=this.constructor.DATA_KEY;(t=t||i.default(e.currentTarget).data(n))||(t=new this.constructor(e.currentTarget,this._getDelegateConfig()),i.default(e.currentTarget).data(n,t)),e&&(t._activeTrigger["focusin"===e.type?Dt:Pt]=!0),i.default(t.getTipElement()).hasClass(It)||t._hoverState===jt?t._hoverState=jt:(clearTimeout(t._timeout),t._hoverState=jt,t.config.delay&&t.config.delay.show?t._timeout=setTimeout((function(){t._hoverState===jt&&t.show()}),t.config.delay.show):t.show())},t._leave=function(e,t){var n=this.constructor.DATA_KEY;(t=t||i.default(e.currentTarget).data(n))||(t=new this.constructor(e.currentTarget,this._getDelegateConfig()),i.default(e.currentTarget).data(n,t)),e&&(t._activeTrigger["focusout"===e.type?Dt:Pt]=!1),t._isWithActiveTrigger()||(clearTimeout(t._timeout),t._hoverState=Ot,t.config.delay&&t.config.delay.hide?t._timeout=setTimeout((function(){t._hoverState===Ot&&t.hide()}),t.config.delay.hide):t.hide())},t._isWithActiveTrigger=function(){for(var e in this._activeTrigger)if(this._activeTrigger[e])return!0;return!1},t._getConfig=function(e){var t=i.default(this.element).data();return Object.keys(t).forEach((function(e){-1!==Ct.indexOf(e)&&delete t[e]})),"number"==typeof(e=l({},this.constructor.Default,t,"object"==typeof e&&e?e:{})).delay&&(e.delay={show:e.delay,hide:e.delay}),"number"==typeof e.title&&(e.title=e.title.toString()),"number"==typeof e.content&&(e.content=e.content.toString()),p.typeCheckConfig(wt,e,this.constructor.DefaultType),e.sanitize&&(e.template=_t(e.template,e.whiteList,e.sanitizeFn)),e},t._getDelegateConfig=function(){var e={};if(this.config)for(var t in this.config)this.constructor.Default[t]!==this.config[t]&&(e[t]=this.config[t]);return e},t._cleanTipClass=function(){var e=i.default(this.getTipElement()),t=e.attr("class").match(Et);null!==t&&t.length&&e.removeClass(t.join(""))},t._handlePopperPlacementChange=function(e){this.tip=e.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(e.placement))},t._fixTransition=function(){var e=this.getTipElement(),t=this.config.animation;null===e.getAttribute("x-placement")&&(i.default(e).removeClass(At),this.config.animation=!1,this.hide(),this.show(),this.config.animation=t)},e._jQueryInterface=function(t){return this.each((function(){var n=i.default(this),r=n.data(xt),o="object"==typeof t&&t;if((r||!/dispose|hide/.test(t))&&(r||(r=new e(this,o),n.data(xt,r)),"string"==typeof t)){if(void 0===r[t])throw new TypeError('No method named "'+t+'"');r[t]()}}))},s(e,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Lt}},{key:"NAME",get:function(){return wt}},{key:"DATA_KEY",get:function(){return xt}},{key:"Event",get:function(){return Rt}},{key:"EVENT_KEY",get:function(){return Tt}},{key:"DefaultType",get:function(){return Mt}}]),e}();i.default.fn[wt]=Ut._jQueryInterface,i.default.fn[wt].Constructor=Ut,i.default.fn[wt].noConflict=function(){return i.default.fn[wt]=kt,Ut._jQueryInterface};var Ft="popover",Bt="bs.popover",zt="."+Bt,qt=i.default.fn[Ft],Ht="bs-popover",$t=new RegExp("(^|\\s)"+Ht+"\\S+","g"),Vt=l({},Ut.Default,{placement:"right",trigger:"click",content:"",template:''}),Wt=l({},Ut.DefaultType,{content:"(string|element|function)"}),Gt={HIDE:"hide"+zt,HIDDEN:"hidden"+zt,SHOW:"show"+zt,SHOWN:"shown"+zt,INSERTED:"inserted"+zt,CLICK:"click"+zt,FOCUSIN:"focusin"+zt,FOCUSOUT:"focusout"+zt,MOUSEENTER:"mouseenter"+zt,MOUSELEAVE:"mouseleave"+zt},Xt=function(e){function t(){return e.apply(this,arguments)||this}var n,r;r=e,(n=t).prototype=Object.create(r.prototype),n.prototype.constructor=n,c(n,r);var o=t.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(e){i.default(this.getTipElement()).addClass(Ht+"-"+e)},o.getTipElement=function(){return this.tip=this.tip||i.default(this.config.template)[0],this.tip},o.setContent=function(){var e=i.default(this.getTipElement());this.setElementContent(e.find(".popover-header"),this.getTitle());var t=this._getContent();"function"==typeof t&&(t=t.call(this.element)),this.setElementContent(e.find(".popover-body"),t),e.removeClass("fade show")},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var e=i.default(this.getTipElement()),t=e.attr("class").match($t);null!==t&&t.length>0&&e.removeClass(t.join(""))},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this).data(Bt),r="object"==typeof e?e:null;if((n||!/dispose|hide/.test(e))&&(n||(n=new t(this,r),i.default(this).data(Bt,n)),"string"==typeof e)){if(void 0===n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},s(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Vt}},{key:"NAME",get:function(){return Ft}},{key:"DATA_KEY",get:function(){return Bt}},{key:"Event",get:function(){return Gt}},{key:"EVENT_KEY",get:function(){return zt}},{key:"DefaultType",get:function(){return Wt}}]),t}(Ut);i.default.fn[Ft]=Xt._jQueryInterface,i.default.fn[Ft].Constructor=Xt,i.default.fn[Ft].noConflict=function(){return i.default.fn[Ft]=qt,Xt._jQueryInterface};var Kt="scrollspy",Yt="bs.scrollspy",Qt="."+Yt,Jt=i.default.fn[Kt],Zt="active",en="activate"+Qt,tn="scroll"+Qt,nn="load"+Qt+".data-api",rn="position",on=".nav, .list-group",an=".nav-link",sn=".list-group-item",ln={offset:10,method:"auto",target:""},cn={offset:"number",method:"string",target:"(string|element)"},un=function(){function e(e,t){var n=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(t),this._selector=this._config.target+" "+an+","+this._config.target+" "+sn+","+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,i.default(this._scrollElement).on(tn,(function(e){return n._process(e)})),this.refresh(),this._process()}var t=e.prototype;return t.refresh=function(){var e=this,t=this._scrollElement===this._scrollElement.window?"offset":rn,n="auto"===this._config.method?t:this._config.method,r=n===rn?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(e){var t,o=p.getSelectorFromElement(e);if(o&&(t=document.querySelector(o)),t){var a=t.getBoundingClientRect();if(a.width||a.height)return[i.default(t)[n]().top+r,o]}return null})).filter(Boolean).sort((function(e,t){return e[0]-t[0]})).forEach((function(t){e._offsets.push(t[0]),e._targets.push(t[1])}))},t.dispose=function(){i.default.removeData(this._element,Yt),i.default(this._scrollElement).off(Qt),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},t._getConfig=function(e){if("string"!=typeof(e=l({},ln,"object"==typeof e&&e?e:{})).target&&p.isElement(e.target)){var t=i.default(e.target).attr("id");t||(t=p.getUID(Kt),i.default(e.target).attr("id",t)),e.target="#"+t}return p.typeCheckConfig(Kt,e,cn),e},t._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},t._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},t._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},t._process=function(){var e=this._getScrollTop()+this._config.offset,t=this._getScrollHeight(),n=this._config.offset+t-this._getOffsetHeight();if(this._scrollHeight!==t&&this.refresh(),e>=n){var r=this._targets[this._targets.length-1];this._activeTarget!==r&&this._activate(r)}else{if(this._activeTarget&&e0)return this._activeTarget=null,void this._clear();for(var i=this._offsets.length;i--;)this._activeTarget!==this._targets[i]&&e>=this._offsets[i]&&(void 0===this._offsets[i+1]||e li > .active",Sn=function(){function e(e){this._element=e}var t=e.prototype;return t.show=function(){var e=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&i.default(this._element).hasClass(mn)||i.default(this._element).hasClass("disabled")||this._element.hasAttribute("disabled"))){var t,n,r=i.default(this._element).closest(".nav, .list-group")[0],o=p.getSelectorFromElement(this._element);if(r){var a="UL"===r.nodeName||"OL"===r.nodeName?kn:Tn;n=(n=i.default.makeArray(i.default(r).find(a)))[n.length-1]}var s=i.default.Event(bn,{relatedTarget:this._element}),l=i.default.Event(_n,{relatedTarget:n});if(n&&i.default(n).trigger(s),i.default(this._element).trigger(l),!l.isDefaultPrevented()&&!s.isDefaultPrevented()){o&&(t=document.querySelector(o)),this._activate(this._element,r);var c=function(){var t=i.default.Event(yn,{relatedTarget:e._element}),r=i.default.Event(wn,{relatedTarget:n});i.default(n).trigger(t),i.default(e._element).trigger(r)};t?this._activate(t,t.parentNode,c):c()}}},t.dispose=function(){i.default.removeData(this._element,pn),this._element=null},t._activate=function(e,t,n){var r=this,o=(!t||"UL"!==t.nodeName&&"OL"!==t.nodeName?i.default(t).children(Tn):i.default(t).find(kn))[0],a=n&&o&&i.default(o).hasClass(gn),s=function(){return r._transitionComplete(e,o,n)};if(o&&a){var l=p.getTransitionDurationFromElement(o);i.default(o).removeClass(vn).one(p.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},t._transitionComplete=function(e,t,n){if(t){i.default(t).removeClass(mn);var r=i.default(t.parentNode).find("> .dropdown-menu .active")[0];r&&i.default(r).removeClass(mn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!1)}i.default(e).addClass(mn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!0),p.reflow(e),e.classList.contains(gn)&&e.classList.add(vn);var o=e.parentNode;if(o&&"LI"===o.nodeName&&(o=o.parentNode),o&&i.default(o).hasClass("dropdown-menu")){var a=i.default(e).closest(".dropdown")[0];if(a){var s=[].slice.call(a.querySelectorAll(".dropdown-toggle"));i.default(s).addClass(mn)}e.setAttribute("aria-expanded",!0)}n&&n()},e._jQueryInterface=function(t){return this.each((function(){var n=i.default(this),r=n.data(pn);if(r||(r=new e(this),n.data(pn,r)),"string"==typeof t){if(void 0===r[t])throw new TypeError('No method named "'+t+'"');r[t]()}}))},s(e,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),e}();i.default(document).on(xn,'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(e){e.preventDefault(),Sn._jQueryInterface.call(i.default(this),"show")})),i.default.fn[dn]=Sn._jQueryInterface,i.default.fn[dn].Constructor=Sn,i.default.fn[dn].noConflict=function(){return i.default.fn[dn]=fn,Sn._jQueryInterface};var En="toast",Cn="bs.toast",An="."+Cn,In=i.default.fn[En],jn="hide",On="show",Pn="showing",Dn="click.dismiss"+An,Nn="hide"+An,Ln="hidden"+An,Mn="show"+An,Rn="shown"+An,Un={animation:!0,autohide:!0,delay:500},Fn={animation:"boolean",autohide:"boolean",delay:"number"},Bn=function(){function e(e,t){this._element=e,this._config=this._getConfig(t),this._timeout=null,this._setListeners()}var t=e.prototype;return t.show=function(){var e=this,t=i.default.Event(Mn);if(i.default(this._element).trigger(t),!t.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){e._element.classList.remove(Pn),e._element.classList.add(On),i.default(e._element).trigger(Rn),e._config.autohide&&(e._timeout=setTimeout((function(){e.hide()}),e._config.delay))};if(this._element.classList.remove(jn),p.reflow(this._element),this._element.classList.add(Pn),this._config.animation){var r=p.getTransitionDurationFromElement(this._element);i.default(this._element).one(p.TRANSITION_END,n).emulateTransitionEnd(r)}else n()}},t.hide=function(){if(this._element.classList.contains(On)){var e=i.default.Event(Nn);i.default(this._element).trigger(e),e.isDefaultPrevented()||this._close()}},t.dispose=function(){this._clearTimeout(),this._element.classList.contains(On)&&this._element.classList.remove(On),i.default(this._element).off(Dn),i.default.removeData(this._element,Cn),this._element=null,this._config=null},t._getConfig=function(e){return e=l({},Un,i.default(this._element).data(),"object"==typeof e&&e?e:{}),p.typeCheckConfig(En,e,this.constructor.DefaultType),e},t._setListeners=function(){var e=this;i.default(this._element).on(Dn,'[data-dismiss="toast"]',(function(){return e.hide()}))},t._close=function(){var e=this,t=function(){e._element.classList.add(jn),i.default(e._element).trigger(Ln)};if(this._element.classList.remove(On),this._config.animation){var n=p.getTransitionDurationFromElement(this._element);i.default(this._element).one(p.TRANSITION_END,t).emulateTransitionEnd(n)}else t()},t._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},e._jQueryInterface=function(t){return this.each((function(){var n=i.default(this),r=n.data(Cn);if(r||(r=new e(this,"object"==typeof t&&t),n.data(Cn,r)),"string"==typeof t){if(void 0===r[t])throw new TypeError('No method named "'+t+'"');r[t](this)}}))},s(e,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"DefaultType",get:function(){return Fn}},{key:"Default",get:function(){return Un}}]),e}();i.default.fn[En]=Bn._jQueryInterface,i.default.fn[En].Constructor=Bn,i.default.fn[En].noConflict=function(){return i.default.fn[En]=In,Bn._jQueryInterface},e.Alert=_,e.Button=D,e.Carousel=oe,e.Collapse=Te,e.Dropdown=Ve,e.Modal=gt,e.Popover=Xt,e.Scrollspy=un,e.Tab=Sn,e.Toast=Bn,e.Tooltip=Ut,e.Util=p,Object.defineProperty(e,"__esModule",{value:!0})}(t,n(4692),n(8851))},3024:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o),s=n(4417),l=n.n(s),c=new URL(n(7422),n.b),u=a()(i()),d=l()(c);u.push([e.id,`.vjs-svg-icon {\n display: inline-block;\n background-repeat: no-repeat;\n background-position: center;\n fill: currentColor;\n height: 1.8em;\n width: 1.8em;\n}\n.vjs-svg-icon:before {\n content: none !important;\n}\n\n.vjs-svg-icon:hover,\n.vjs-control:focus .vjs-svg-icon {\n filter: drop-shadow(0 0 0.25em #fff);\n}\n\n.vjs-modal-dialog .vjs-modal-dialog-content, .video-js .vjs-modal-dialog, .vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.vjs-button > .vjs-icon-placeholder:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before {\n text-align: center;\n}\n\n@font-face {\n font-family: VideoJS;\n src: url(${d}) format("woff");\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-play, .video-js .vjs-play-control .vjs-icon-placeholder, .video-js .vjs-big-play-button .vjs-icon-placeholder:before {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-play:before, .video-js .vjs-play-control .vjs-icon-placeholder:before, .video-js .vjs-big-play-button .vjs-icon-placeholder:before {\n content: "\\f101";\n}\n\n.vjs-icon-play-circle {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-play-circle:before {\n content: "\\f102";\n}\n\n.vjs-icon-pause, .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-pause:before, .video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before {\n content: "\\f103";\n}\n\n.vjs-icon-volume-mute, .video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-volume-mute:before, .video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before {\n content: "\\f104";\n}\n\n.vjs-icon-volume-low, .video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-volume-low:before, .video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before {\n content: "\\f105";\n}\n\n.vjs-icon-volume-mid, .video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-volume-mid:before, .video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before {\n content: "\\f106";\n}\n\n.vjs-icon-volume-high, .video-js .vjs-mute-control .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-volume-high:before, .video-js .vjs-mute-control .vjs-icon-placeholder:before {\n content: "\\f107";\n}\n\n.vjs-icon-fullscreen-enter, .video-js .vjs-fullscreen-control .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-fullscreen-enter:before, .video-js .vjs-fullscreen-control .vjs-icon-placeholder:before {\n content: "\\f108";\n}\n\n.vjs-icon-fullscreen-exit, .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-fullscreen-exit:before, .video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before {\n content: "\\f109";\n}\n\n.vjs-icon-spinner {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-spinner:before {\n content: "\\f10a";\n}\n\n.vjs-icon-subtitles, .video-js .vjs-subs-caps-button .vjs-icon-placeholder,\n.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder,\n.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder,\n.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder,\n.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder, .video-js .vjs-subtitles-button .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-subtitles:before, .video-js .vjs-subs-caps-button .vjs-icon-placeholder:before,\n.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder:before,\n.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder:before,\n.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder:before,\n.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before, .video-js .vjs-subtitles-button .vjs-icon-placeholder:before {\n content: "\\f10b";\n}\n\n.vjs-icon-captions, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder,\n.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder, .video-js .vjs-captions-button .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-captions:before, .video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder:before,\n.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder:before, .video-js .vjs-captions-button .vjs-icon-placeholder:before {\n content: "\\f10c";\n}\n\n.vjs-icon-hd {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-hd:before {\n content: "\\f10d";\n}\n\n.vjs-icon-chapters, .video-js .vjs-chapters-button .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-chapters:before, .video-js .vjs-chapters-button .vjs-icon-placeholder:before {\n content: "\\f10e";\n}\n\n.vjs-icon-downloading {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-downloading:before {\n content: "\\f10f";\n}\n\n.vjs-icon-file-download {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-file-download:before {\n content: "\\f110";\n}\n\n.vjs-icon-file-download-done {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-file-download-done:before {\n content: "\\f111";\n}\n\n.vjs-icon-file-download-off {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-file-download-off:before {\n content: "\\f112";\n}\n\n.vjs-icon-share {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-share:before {\n content: "\\f113";\n}\n\n.vjs-icon-cog {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-cog:before {\n content: "\\f114";\n}\n\n.vjs-icon-square {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-square:before {\n content: "\\f115";\n}\n\n.vjs-icon-circle, .vjs-seek-to-live-control .vjs-icon-placeholder, .video-js .vjs-volume-level, .video-js .vjs-play-progress {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-circle:before, .vjs-seek-to-live-control .vjs-icon-placeholder:before, .video-js .vjs-volume-level:before, .video-js .vjs-play-progress:before {\n content: "\\f116";\n}\n\n.vjs-icon-circle-outline {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-circle-outline:before {\n content: "\\f117";\n}\n\n.vjs-icon-circle-inner-circle {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-circle-inner-circle:before {\n content: "\\f118";\n}\n\n.vjs-icon-cancel, .video-js .vjs-control.vjs-close-button .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-cancel:before, .video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before {\n content: "\\f119";\n}\n\n.vjs-icon-repeat {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-repeat:before {\n content: "\\f11a";\n}\n\n.vjs-icon-replay, .video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-replay:before, .video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before {\n content: "\\f11b";\n}\n\n.vjs-icon-replay-5, .video-js .vjs-skip-backward-5 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-replay-5:before, .video-js .vjs-skip-backward-5 .vjs-icon-placeholder:before {\n content: "\\f11c";\n}\n\n.vjs-icon-replay-10, .video-js .vjs-skip-backward-10 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-replay-10:before, .video-js .vjs-skip-backward-10 .vjs-icon-placeholder:before {\n content: "\\f11d";\n}\n\n.vjs-icon-replay-30, .video-js .vjs-skip-backward-30 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-replay-30:before, .video-js .vjs-skip-backward-30 .vjs-icon-placeholder:before {\n content: "\\f11e";\n}\n\n.vjs-icon-forward-5, .video-js .vjs-skip-forward-5 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-forward-5:before, .video-js .vjs-skip-forward-5 .vjs-icon-placeholder:before {\n content: "\\f11f";\n}\n\n.vjs-icon-forward-10, .video-js .vjs-skip-forward-10 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-forward-10:before, .video-js .vjs-skip-forward-10 .vjs-icon-placeholder:before {\n content: "\\f120";\n}\n\n.vjs-icon-forward-30, .video-js .vjs-skip-forward-30 .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-forward-30:before, .video-js .vjs-skip-forward-30 .vjs-icon-placeholder:before {\n content: "\\f121";\n}\n\n.vjs-icon-audio, .video-js .vjs-audio-button .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-audio:before, .video-js .vjs-audio-button .vjs-icon-placeholder:before {\n content: "\\f122";\n}\n\n.vjs-icon-next-item {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-next-item:before {\n content: "\\f123";\n}\n\n.vjs-icon-previous-item {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-previous-item:before {\n content: "\\f124";\n}\n\n.vjs-icon-shuffle {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-shuffle:before {\n content: "\\f125";\n}\n\n.vjs-icon-cast {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-cast:before {\n content: "\\f126";\n}\n\n.vjs-icon-picture-in-picture-enter, .video-js .vjs-picture-in-picture-control .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-picture-in-picture-enter:before, .video-js .vjs-picture-in-picture-control .vjs-icon-placeholder:before {\n content: "\\f127";\n}\n\n.vjs-icon-picture-in-picture-exit, .video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-picture-in-picture-exit:before, .video-js.vjs-picture-in-picture .vjs-picture-in-picture-control .vjs-icon-placeholder:before {\n content: "\\f128";\n}\n\n.vjs-icon-facebook {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-facebook:before {\n content: "\\f129";\n}\n\n.vjs-icon-linkedin {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-linkedin:before {\n content: "\\f12a";\n}\n\n.vjs-icon-twitter {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-twitter:before {\n content: "\\f12b";\n}\n\n.vjs-icon-tumblr {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-tumblr:before {\n content: "\\f12c";\n}\n\n.vjs-icon-pinterest {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-pinterest:before {\n content: "\\f12d";\n}\n\n.vjs-icon-audio-description, .video-js .vjs-descriptions-button .vjs-icon-placeholder {\n font-family: VideoJS;\n font-weight: normal;\n font-style: normal;\n}\n.vjs-icon-audio-description:before, .video-js .vjs-descriptions-button .vjs-icon-placeholder:before {\n content: "\\f12e";\n}\n\n.video-js {\n display: inline-block;\n vertical-align: top;\n box-sizing: border-box;\n color: #fff;\n background-color: #000;\n position: relative;\n padding: 0;\n font-size: 10px;\n line-height: 1;\n font-weight: normal;\n font-style: normal;\n font-family: Arial, Helvetica, sans-serif;\n word-break: initial;\n}\n.video-js:-moz-full-screen {\n position: absolute;\n}\n.video-js:-webkit-full-screen {\n width: 100% !important;\n height: 100% !important;\n}\n\n.video-js[tabindex="-1"] {\n outline: none;\n}\n\n.video-js *,\n.video-js *:before,\n.video-js *:after {\n box-sizing: inherit;\n}\n\n.video-js ul {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n list-style-position: outside;\n margin-left: 0;\n margin-right: 0;\n margin-top: 0;\n margin-bottom: 0;\n}\n\n.video-js.vjs-fluid,\n.video-js.vjs-16-9,\n.video-js.vjs-4-3,\n.video-js.vjs-9-16,\n.video-js.vjs-1-1 {\n width: 100%;\n max-width: 100%;\n}\n\n.video-js.vjs-fluid:not(.vjs-audio-only-mode),\n.video-js.vjs-16-9:not(.vjs-audio-only-mode),\n.video-js.vjs-4-3:not(.vjs-audio-only-mode),\n.video-js.vjs-9-16:not(.vjs-audio-only-mode),\n.video-js.vjs-1-1:not(.vjs-audio-only-mode) {\n height: 0;\n}\n\n.video-js.vjs-16-9:not(.vjs-audio-only-mode) {\n padding-top: 56.25%;\n}\n\n.video-js.vjs-4-3:not(.vjs-audio-only-mode) {\n padding-top: 75%;\n}\n\n.video-js.vjs-9-16:not(.vjs-audio-only-mode) {\n padding-top: 177.7777777778%;\n}\n\n.video-js.vjs-1-1:not(.vjs-audio-only-mode) {\n padding-top: 100%;\n}\n\n.video-js.vjs-fill:not(.vjs-audio-only-mode) {\n width: 100%;\n height: 100%;\n}\n\n.video-js .vjs-tech {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.video-js.vjs-audio-only-mode .vjs-tech {\n display: none;\n}\n\nbody.vjs-full-window,\nbody.vjs-pip-window {\n padding: 0;\n margin: 0;\n height: 100%;\n}\n\n.vjs-full-window .video-js.vjs-fullscreen,\nbody.vjs-pip-window .video-js {\n position: fixed;\n overflow: hidden;\n z-index: 1000;\n left: 0;\n top: 0;\n bottom: 0;\n right: 0;\n}\n\n.video-js.vjs-fullscreen:not(.vjs-ios-native-fs),\nbody.vjs-pip-window .video-js {\n width: 100% !important;\n height: 100% !important;\n padding-top: 0 !important;\n display: block;\n}\n\n.video-js.vjs-fullscreen.vjs-user-inactive {\n cursor: none;\n}\n\n.vjs-pip-container .vjs-pip-text {\n position: absolute;\n bottom: 10%;\n font-size: 2em;\n background-color: rgba(0, 0, 0, 0.7);\n padding: 0.5em;\n text-align: center;\n width: 100%;\n}\n\n.vjs-layout-tiny.vjs-pip-container .vjs-pip-text,\n.vjs-layout-x-small.vjs-pip-container .vjs-pip-text,\n.vjs-layout-small.vjs-pip-container .vjs-pip-text {\n bottom: 0;\n font-size: 1.4em;\n}\n\n.vjs-hidden {\n display: none !important;\n}\n\n.vjs-disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.video-js .vjs-offscreen {\n height: 1px;\n left: -9999px;\n position: absolute;\n top: 0;\n width: 1px;\n}\n\n.vjs-lock-showing {\n display: block !important;\n opacity: 1 !important;\n visibility: visible !important;\n}\n\n.vjs-no-js {\n padding: 20px;\n color: #fff;\n background-color: #000;\n font-size: 18px;\n font-family: Arial, Helvetica, sans-serif;\n text-align: center;\n width: 300px;\n height: 150px;\n margin: 0px auto;\n}\n\n.vjs-no-js a,\n.vjs-no-js a:visited {\n color: #66A8CC;\n}\n\n.video-js .vjs-big-play-button {\n font-size: 3em;\n line-height: 1.5em;\n height: 1.63332em;\n width: 3em;\n display: block;\n position: absolute;\n top: 50%;\n left: 50%;\n padding: 0;\n margin-top: -0.81666em;\n margin-left: -1.5em;\n cursor: pointer;\n opacity: 1;\n border: 0.06666em solid #fff;\n background-color: #2B333F;\n background-color: rgba(43, 51, 63, 0.7);\n border-radius: 0.3em;\n transition: all 0.4s;\n}\n.vjs-big-play-button .vjs-svg-icon {\n width: 1em;\n height: 1em;\n position: absolute;\n top: 50%;\n left: 50%;\n line-height: 1;\n transform: translate(-50%, -50%);\n}\n\n.video-js:hover .vjs-big-play-button,\n.video-js .vjs-big-play-button:focus {\n border-color: #fff;\n background-color: #73859f;\n background-color: rgba(115, 133, 159, 0.5);\n transition: all 0s;\n}\n\n.vjs-controls-disabled .vjs-big-play-button,\n.vjs-has-started .vjs-big-play-button,\n.vjs-using-native-controls .vjs-big-play-button,\n.vjs-error .vjs-big-play-button {\n display: none;\n}\n\n.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause:not(.vjs-seeking, .vjs-scrubbing, .vjs-error) .vjs-big-play-button {\n display: block;\n}\n\n.video-js button {\n background: none;\n border: none;\n color: inherit;\n display: inline-block;\n font-size: inherit;\n line-height: inherit;\n text-transform: none;\n text-decoration: none;\n transition: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.video-js.vjs-spatial-navigation-enabled .vjs-button:focus {\n outline: 0.0625em solid white;\n box-shadow: none;\n}\n\n.vjs-control .vjs-button {\n width: 100%;\n height: 100%;\n}\n\n.video-js .vjs-control.vjs-close-button {\n cursor: pointer;\n height: 3em;\n position: absolute;\n right: 0;\n top: 0.5em;\n z-index: 2;\n}\n.video-js .vjs-modal-dialog {\n background: rgba(0, 0, 0, 0.8);\n background: linear-gradient(180deg, rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0));\n overflow: auto;\n}\n\n.video-js .vjs-modal-dialog > * {\n box-sizing: border-box;\n}\n\n.vjs-modal-dialog .vjs-modal-dialog-content {\n font-size: 1.2em;\n line-height: 1.5;\n padding: 20px 24px;\n z-index: 1;\n}\n\n.vjs-menu-button {\n cursor: pointer;\n}\n\n.vjs-menu-button.vjs-disabled {\n cursor: default;\n}\n\n.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu {\n display: none;\n}\n\n.vjs-menu .vjs-menu-content {\n display: block;\n padding: 0;\n margin: 0;\n font-family: Arial, Helvetica, sans-serif;\n overflow: auto;\n}\n\n.vjs-menu .vjs-menu-content > * {\n box-sizing: border-box;\n}\n\n.vjs-scrubbing .vjs-control.vjs-menu-button:hover .vjs-menu {\n display: none;\n}\n\n.vjs-menu li {\n display: flex;\n justify-content: center;\n list-style: none;\n margin: 0;\n padding: 0.2em 0;\n line-height: 1.4em;\n font-size: 1.2em;\n text-align: center;\n text-transform: lowercase;\n}\n\n.vjs-menu li.vjs-menu-item:focus,\n.vjs-menu li.vjs-menu-item:hover,\n.js-focus-visible .vjs-menu li.vjs-menu-item:hover {\n background-color: #73859f;\n background-color: rgba(115, 133, 159, 0.5);\n}\n\n.vjs-menu li.vjs-selected,\n.vjs-menu li.vjs-selected:focus,\n.vjs-menu li.vjs-selected:hover,\n.js-focus-visible .vjs-menu li.vjs-selected:hover {\n background-color: #fff;\n color: #2B333F;\n}\n.vjs-menu li.vjs-selected .vjs-svg-icon,\n.vjs-menu li.vjs-selected:focus .vjs-svg-icon,\n.vjs-menu li.vjs-selected:hover .vjs-svg-icon,\n.js-focus-visible .vjs-menu li.vjs-selected:hover .vjs-svg-icon {\n fill: #000000;\n}\n\n.video-js .vjs-menu *:not(.vjs-selected):focus:not(:focus-visible),\n.js-focus-visible .vjs-menu *:not(.vjs-selected):focus:not(.focus-visible) {\n background: none;\n}\n\n.vjs-menu li.vjs-menu-title {\n text-align: center;\n text-transform: uppercase;\n font-size: 1em;\n line-height: 2em;\n padding: 0;\n margin: 0 0 0.3em 0;\n font-weight: bold;\n cursor: default;\n}\n\n.vjs-menu-button-popup .vjs-menu {\n display: none;\n position: absolute;\n bottom: 0;\n width: 10em;\n left: -3em;\n height: 0em;\n margin-bottom: 1.5em;\n border-top-color: rgba(43, 51, 63, 0.7);\n}\n\n.vjs-pip-window .vjs-menu-button-popup .vjs-menu {\n left: unset;\n right: 1em;\n}\n\n.vjs-menu-button-popup .vjs-menu .vjs-menu-content {\n background-color: #2B333F;\n background-color: rgba(43, 51, 63, 0.7);\n position: absolute;\n width: 100%;\n bottom: 1.5em;\n max-height: 15em;\n}\n\n.vjs-layout-tiny .vjs-menu-button-popup .vjs-menu .vjs-menu-content,\n.vjs-layout-x-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content {\n max-height: 5em;\n}\n\n.vjs-layout-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content {\n max-height: 10em;\n}\n\n.vjs-layout-medium .vjs-menu-button-popup .vjs-menu .vjs-menu-content {\n max-height: 14em;\n}\n\n.vjs-layout-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content,\n.vjs-layout-x-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content,\n.vjs-layout-huge .vjs-menu-button-popup .vjs-menu .vjs-menu-content {\n max-height: 25em;\n}\n\n.vjs-workinghover .vjs-menu-button-popup.vjs-hover .vjs-menu,\n.vjs-menu-button-popup .vjs-menu.vjs-lock-showing {\n display: block;\n}\n\n.video-js .vjs-menu-button-inline {\n transition: all 0.4s;\n overflow: hidden;\n}\n\n.video-js .vjs-menu-button-inline:before {\n width: 2.222222222em;\n}\n\n.video-js .vjs-menu-button-inline:hover,\n.video-js .vjs-menu-button-inline:focus,\n.video-js .vjs-menu-button-inline.vjs-slider-active {\n width: 12em;\n}\n\n.vjs-menu-button-inline .vjs-menu {\n opacity: 0;\n height: 100%;\n width: auto;\n position: absolute;\n left: 4em;\n top: 0;\n padding: 0;\n margin: 0;\n transition: all 0.4s;\n}\n\n.vjs-menu-button-inline:hover .vjs-menu,\n.vjs-menu-button-inline:focus .vjs-menu,\n.vjs-menu-button-inline.vjs-slider-active .vjs-menu {\n display: block;\n opacity: 1;\n}\n\n.vjs-menu-button-inline .vjs-menu-content {\n width: auto;\n height: 100%;\n margin: 0;\n overflow: hidden;\n}\n\n.video-js .vjs-control-bar {\n display: none;\n width: 100%;\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 3em;\n background-color: #2B333F;\n background-color: rgba(43, 51, 63, 0.7);\n}\n\n.video-js.vjs-spatial-navigation-enabled .vjs-control-bar {\n gap: 1px;\n}\n\n.video-js:not(.vjs-controls-disabled, .vjs-using-native-controls, .vjs-error) .vjs-control-bar.vjs-lock-showing {\n display: flex !important;\n}\n\n.vjs-has-started .vjs-control-bar,\n.vjs-audio-only-mode .vjs-control-bar {\n display: flex;\n visibility: visible;\n opacity: 1;\n transition: visibility 0.1s, opacity 0.1s;\n}\n\n.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {\n visibility: visible;\n opacity: 0;\n pointer-events: none;\n transition: visibility 1s, opacity 1s;\n}\n\n.vjs-controls-disabled .vjs-control-bar,\n.vjs-using-native-controls .vjs-control-bar,\n.vjs-error .vjs-control-bar {\n display: none !important;\n}\n\n.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar,\n.vjs-audio-only-mode.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n}\n\n.video-js .vjs-control {\n position: relative;\n text-align: center;\n margin: 0;\n padding: 0;\n height: 100%;\n width: 4em;\n flex: none;\n}\n\n.video-js .vjs-control.vjs-visible-text {\n width: auto;\n padding-left: 1em;\n padding-right: 1em;\n}\n\n.vjs-button > .vjs-icon-placeholder:before {\n font-size: 1.8em;\n line-height: 1.67;\n}\n\n.vjs-button > .vjs-icon-placeholder {\n display: block;\n}\n\n.vjs-button > .vjs-svg-icon {\n display: inline-block;\n}\n\n.video-js .vjs-control:focus:before,\n.video-js .vjs-control:hover:before,\n.video-js .vjs-control:focus {\n text-shadow: 0em 0em 1em white;\n}\n\n.video-js *:not(.vjs-visible-text) > .vjs-control-text {\n border: 0;\n clip: rect(0 0 0 0);\n height: 1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n width: 1px;\n}\n\n.video-js .vjs-custom-control-spacer {\n display: none;\n}\n\n.video-js .vjs-progress-control {\n cursor: pointer;\n flex: auto;\n display: flex;\n align-items: center;\n min-width: 4em;\n touch-action: none;\n}\n\n.video-js .vjs-progress-control.disabled {\n cursor: default;\n}\n\n.vjs-live .vjs-progress-control {\n display: none;\n}\n\n.vjs-liveui .vjs-progress-control {\n display: flex;\n align-items: center;\n}\n\n.video-js .vjs-progress-holder {\n flex: auto;\n transition: all 0.2s;\n height: 0.3em;\n}\n\n.video-js .vjs-progress-control .vjs-progress-holder {\n margin: 0 10px;\n}\n\n.video-js .vjs-progress-control:hover .vjs-progress-holder {\n font-size: 1.6666666667em;\n}\n\n.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled {\n font-size: 1em;\n}\n\n.video-js .vjs-progress-holder .vjs-play-progress,\n.video-js .vjs-progress-holder .vjs-load-progress,\n.video-js .vjs-progress-holder .vjs-load-progress div {\n position: absolute;\n display: block;\n height: 100%;\n margin: 0;\n padding: 0;\n width: 0;\n}\n\n.video-js .vjs-play-progress {\n background-color: #fff;\n}\n.video-js .vjs-play-progress:before {\n font-size: 0.9em;\n position: absolute;\n right: -0.5em;\n line-height: 0.35em;\n z-index: 1;\n}\n\n.vjs-svg-icons-enabled .vjs-play-progress:before {\n content: none !important;\n}\n\n.vjs-play-progress .vjs-svg-icon {\n position: absolute;\n top: -0.35em;\n right: -0.4em;\n width: 0.9em;\n height: 0.9em;\n pointer-events: none;\n line-height: 0.15em;\n z-index: 1;\n}\n\n.video-js .vjs-load-progress {\n background: rgba(115, 133, 159, 0.5);\n}\n\n.video-js .vjs-load-progress div {\n background: rgba(115, 133, 159, 0.75);\n}\n\n.video-js .vjs-time-tooltip {\n background-color: #fff;\n background-color: rgba(255, 255, 255, 0.8);\n border-radius: 0.3em;\n color: #000;\n float: right;\n font-family: Arial, Helvetica, sans-serif;\n font-size: 1em;\n padding: 6px 8px 8px 8px;\n pointer-events: none;\n position: absolute;\n top: -3.4em;\n visibility: hidden;\n z-index: 1;\n}\n\n.video-js .vjs-progress-holder:focus .vjs-time-tooltip {\n display: none;\n}\n\n.video-js .vjs-progress-control:hover .vjs-time-tooltip,\n.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip {\n display: block;\n font-size: 0.6em;\n visibility: visible;\n}\n\n.video-js .vjs-progress-control.disabled:hover .vjs-time-tooltip {\n font-size: 1em;\n}\n\n.video-js .vjs-progress-control .vjs-mouse-display {\n display: none;\n position: absolute;\n width: 1px;\n height: 100%;\n background-color: #000;\n z-index: 1;\n}\n\n.video-js .vjs-progress-control:hover .vjs-mouse-display {\n display: block;\n}\n\n.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display {\n visibility: hidden;\n opacity: 0;\n transition: visibility 1s, opacity 1s;\n}\n\n.vjs-mouse-display .vjs-time-tooltip {\n color: #fff;\n background-color: #000;\n background-color: rgba(0, 0, 0, 0.8);\n}\n\n.video-js .vjs-slider {\n position: relative;\n cursor: pointer;\n padding: 0;\n margin: 0 0.45em 0 0.45em;\n /* iOS Safari */\n -webkit-touch-callout: none;\n /* Safari, and Chrome 53 */\n -webkit-user-select: none;\n /* Non-prefixed version, currently supported by Chrome and Opera */\n -moz-user-select: none;\n user-select: none;\n background-color: #73859f;\n background-color: rgba(115, 133, 159, 0.5);\n}\n\n.video-js .vjs-slider.disabled {\n cursor: default;\n}\n\n.video-js .vjs-slider:focus {\n text-shadow: 0em 0em 1em white;\n box-shadow: 0 0 1em #fff;\n}\n\n.video-js.vjs-spatial-navigation-enabled .vjs-slider:focus {\n outline: 0.0625em solid white;\n}\n\n.video-js .vjs-mute-control {\n cursor: pointer;\n flex: none;\n}\n.video-js .vjs-volume-control {\n cursor: pointer;\n margin-right: 1em;\n display: flex;\n}\n\n.video-js .vjs-volume-control.vjs-volume-horizontal {\n width: 5em;\n}\n\n.video-js .vjs-volume-panel .vjs-volume-control {\n visibility: visible;\n opacity: 0;\n width: 1px;\n height: 1px;\n margin-left: -1px;\n}\n\n.video-js .vjs-volume-panel {\n transition: width 1s;\n}\n.video-js .vjs-volume-panel.vjs-hover .vjs-volume-control, .video-js .vjs-volume-panel:active .vjs-volume-control, .video-js .vjs-volume-panel:focus .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control:active, .video-js .vjs-volume-panel.vjs-hover .vjs-mute-control ~ .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active {\n visibility: visible;\n opacity: 1;\n position: relative;\n transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s;\n}\n.video-js .vjs-volume-panel.vjs-hover .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal, .video-js .vjs-volume-panel.vjs-hover .vjs-mute-control ~ .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal {\n width: 5em;\n height: 3em;\n margin-right: 0;\n}\n.video-js .vjs-volume-panel.vjs-hover .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical, .video-js .vjs-volume-panel.vjs-hover .vjs-mute-control ~ .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical {\n left: -3.5em;\n transition: left 0s;\n}\n.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active {\n width: 10em;\n transition: width 0.1s;\n}\n.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only {\n width: 4em;\n}\n\n.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical {\n height: 8em;\n width: 3em;\n left: -3000em;\n transition: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s;\n}\n\n.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {\n transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s;\n}\n\n.video-js .vjs-volume-panel {\n display: flex;\n}\n\n.video-js .vjs-volume-bar {\n margin: 1.35em 0.45em;\n}\n\n.vjs-volume-bar.vjs-slider-horizontal {\n width: 5em;\n height: 0.3em;\n}\n\n.vjs-volume-bar.vjs-slider-vertical {\n width: 0.3em;\n height: 5em;\n margin: 1.35em auto;\n}\n\n.video-js .vjs-volume-level {\n position: absolute;\n bottom: 0;\n left: 0;\n background-color: #fff;\n}\n.video-js .vjs-volume-level:before {\n position: absolute;\n font-size: 0.9em;\n z-index: 1;\n}\n\n.vjs-slider-vertical .vjs-volume-level {\n width: 0.3em;\n}\n.vjs-slider-vertical .vjs-volume-level:before {\n top: -0.5em;\n left: -0.3em;\n z-index: 1;\n}\n\n.vjs-svg-icons-enabled .vjs-volume-level:before {\n content: none;\n}\n\n.vjs-volume-level .vjs-svg-icon {\n position: absolute;\n width: 0.9em;\n height: 0.9em;\n pointer-events: none;\n z-index: 1;\n}\n\n.vjs-slider-horizontal .vjs-volume-level {\n height: 0.3em;\n}\n.vjs-slider-horizontal .vjs-volume-level:before {\n line-height: 0.35em;\n right: -0.5em;\n}\n\n.vjs-slider-horizontal .vjs-volume-level .vjs-svg-icon {\n right: -0.3em;\n transform: translateY(-50%);\n}\n\n.vjs-slider-vertical .vjs-volume-level .vjs-svg-icon {\n top: -0.55em;\n transform: translateX(-50%);\n}\n\n.video-js .vjs-volume-panel.vjs-volume-panel-vertical {\n width: 4em;\n}\n\n.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level {\n height: 100%;\n}\n\n.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level {\n width: 100%;\n}\n\n.video-js .vjs-volume-vertical {\n width: 3em;\n height: 8em;\n bottom: 8em;\n background-color: #2B333F;\n background-color: rgba(43, 51, 63, 0.7);\n}\n\n.video-js .vjs-volume-horizontal .vjs-menu {\n left: -2em;\n}\n\n.video-js .vjs-volume-tooltip {\n background-color: #fff;\n background-color: rgba(255, 255, 255, 0.8);\n border-radius: 0.3em;\n color: #000;\n float: right;\n font-family: Arial, Helvetica, sans-serif;\n font-size: 1em;\n padding: 6px 8px 8px 8px;\n pointer-events: none;\n position: absolute;\n top: -3.4em;\n visibility: hidden;\n z-index: 1;\n}\n\n.video-js .vjs-volume-control:hover .vjs-volume-tooltip,\n.video-js .vjs-volume-control:hover .vjs-progress-holder:focus .vjs-volume-tooltip {\n display: block;\n font-size: 1em;\n visibility: visible;\n}\n\n.video-js .vjs-volume-vertical:hover .vjs-volume-tooltip,\n.video-js .vjs-volume-vertical:hover .vjs-progress-holder:focus .vjs-volume-tooltip {\n left: 1em;\n top: -12px;\n}\n\n.video-js .vjs-volume-control.disabled:hover .vjs-volume-tooltip {\n font-size: 1em;\n}\n\n.video-js .vjs-volume-control .vjs-mouse-display {\n display: none;\n position: absolute;\n width: 100%;\n height: 1px;\n background-color: #000;\n z-index: 1;\n}\n\n.video-js .vjs-volume-horizontal .vjs-mouse-display {\n width: 1px;\n height: 100%;\n}\n\n.video-js .vjs-volume-control:hover .vjs-mouse-display {\n display: block;\n}\n\n.video-js.vjs-user-inactive .vjs-volume-control .vjs-mouse-display {\n visibility: hidden;\n opacity: 0;\n transition: visibility 1s, opacity 1s;\n}\n\n.vjs-mouse-display .vjs-volume-tooltip {\n color: #fff;\n background-color: #000;\n background-color: rgba(0, 0, 0, 0.8);\n}\n\n.vjs-poster {\n display: inline-block;\n vertical-align: middle;\n cursor: pointer;\n margin: 0;\n padding: 0;\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n height: 100%;\n}\n\n.vjs-has-started .vjs-poster,\n.vjs-using-native-controls .vjs-poster {\n display: none;\n}\n\n.vjs-audio.vjs-has-started .vjs-poster,\n.vjs-has-started.vjs-audio-poster-mode .vjs-poster,\n.vjs-pip-container.vjs-has-started .vjs-poster {\n display: block;\n}\n\n.vjs-poster img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.video-js .vjs-live-control {\n display: flex;\n align-items: flex-start;\n flex: auto;\n font-size: 1em;\n line-height: 3em;\n}\n\n.video-js:not(.vjs-live) .vjs-live-control,\n.video-js.vjs-liveui .vjs-live-control {\n display: none;\n}\n\n.video-js .vjs-seek-to-live-control {\n align-items: center;\n cursor: pointer;\n flex: none;\n display: inline-flex;\n height: 100%;\n padding-left: 0.5em;\n padding-right: 0.5em;\n font-size: 1em;\n line-height: 3em;\n width: auto;\n min-width: 4em;\n}\n\n.video-js.vjs-live:not(.vjs-liveui) .vjs-seek-to-live-control,\n.video-js:not(.vjs-live) .vjs-seek-to-live-control {\n display: none;\n}\n\n.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge {\n cursor: auto;\n}\n\n.vjs-seek-to-live-control .vjs-icon-placeholder {\n margin-right: 0.5em;\n color: #888;\n}\n\n.vjs-svg-icons-enabled .vjs-seek-to-live-control {\n line-height: 0;\n}\n\n.vjs-seek-to-live-control .vjs-svg-icon {\n width: 1em;\n height: 1em;\n pointer-events: none;\n fill: #888888;\n}\n\n.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-icon-placeholder {\n color: red;\n}\n\n.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-svg-icon {\n fill: red;\n}\n\n.video-js .vjs-time-control {\n flex: none;\n font-size: 1em;\n line-height: 3em;\n min-width: 2em;\n width: auto;\n padding-left: 1em;\n padding-right: 1em;\n}\n\n.vjs-live .vjs-time-control,\n.vjs-live .vjs-time-divider,\n.video-js .vjs-current-time,\n.video-js .vjs-duration {\n display: none;\n}\n\n.vjs-time-divider {\n display: none;\n line-height: 3em;\n}\n\n.vjs-normalise-time-controls:not(.vjs-live) .vjs-time-control {\n display: flex;\n}\n\n.video-js .vjs-play-control {\n cursor: pointer;\n}\n\n.video-js .vjs-play-control .vjs-icon-placeholder {\n flex: none;\n}\n\n.vjs-text-track-display {\n position: absolute;\n bottom: 3em;\n left: 0;\n right: 0;\n top: 0;\n pointer-events: none;\n}\n\n.vjs-error .vjs-text-track-display {\n display: none;\n}\n\n.video-js.vjs-controls-disabled .vjs-text-track-display,\n.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display {\n bottom: 1em;\n}\n\n.video-js .vjs-text-track {\n font-size: 1.4em;\n text-align: center;\n margin-bottom: 0.1em;\n}\n\n.vjs-subtitles {\n color: #fff;\n}\n\n.vjs-captions {\n color: #fc6;\n}\n\n.vjs-tt-cue {\n display: block;\n}\n\nvideo::-webkit-media-text-track-display {\n transform: translateY(-3em);\n}\n\n.video-js.vjs-controls-disabled video::-webkit-media-text-track-display,\n.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display {\n transform: translateY(-1.5em);\n}\n\n.video-js.vjs-force-center-align-cues .vjs-text-track-cue {\n text-align: center !important;\n width: 80% !important;\n}\n\n@supports not (inset: 10px) {\n .video-js .vjs-text-track-display > div {\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n}\n.video-js .vjs-picture-in-picture-control {\n cursor: pointer;\n flex: none;\n}\n.video-js.vjs-audio-only-mode .vjs-picture-in-picture-control,\n.vjs-pip-window .vjs-picture-in-picture-control {\n display: none;\n}\n\n.video-js .vjs-fullscreen-control {\n cursor: pointer;\n flex: none;\n}\n.video-js.vjs-audio-only-mode .vjs-fullscreen-control,\n.vjs-pip-window .vjs-fullscreen-control {\n display: none;\n}\n\n.vjs-playback-rate > .vjs-menu-button,\n.vjs-playback-rate .vjs-playback-rate-value {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.vjs-playback-rate .vjs-playback-rate-value {\n pointer-events: none;\n font-size: 1.5em;\n line-height: 2;\n text-align: center;\n}\n\n.vjs-playback-rate .vjs-menu {\n width: 4em;\n left: 0em;\n}\n\n.vjs-error .vjs-error-display .vjs-modal-dialog-content {\n font-size: 1.4em;\n text-align: center;\n}\n\n.vjs-loading-spinner {\n display: none;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n opacity: 0.85;\n text-align: left;\n border: 0.6em solid rgba(43, 51, 63, 0.7);\n box-sizing: border-box;\n background-clip: padding-box;\n width: 5em;\n height: 5em;\n border-radius: 50%;\n visibility: hidden;\n}\n\n.vjs-seeking .vjs-loading-spinner,\n.vjs-waiting .vjs-loading-spinner {\n display: flex;\n justify-content: center;\n align-items: center;\n animation: vjs-spinner-show 0s linear 0.3s forwards;\n}\n\n.vjs-error .vjs-loading-spinner {\n display: none;\n}\n\n.vjs-loading-spinner:before,\n.vjs-loading-spinner:after {\n content: "";\n position: absolute;\n box-sizing: inherit;\n width: inherit;\n height: inherit;\n border-radius: inherit;\n opacity: 1;\n border: inherit;\n border-color: transparent;\n border-top-color: white;\n}\n\n.vjs-seeking .vjs-loading-spinner:before,\n.vjs-seeking .vjs-loading-spinner:after,\n.vjs-waiting .vjs-loading-spinner:before,\n.vjs-waiting .vjs-loading-spinner:after {\n animation: vjs-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, vjs-spinner-fade 1.1s linear infinite;\n}\n\n.vjs-seeking .vjs-loading-spinner:before,\n.vjs-waiting .vjs-loading-spinner:before {\n border-top-color: rgb(255, 255, 255);\n}\n\n.vjs-seeking .vjs-loading-spinner:after,\n.vjs-waiting .vjs-loading-spinner:after {\n border-top-color: rgb(255, 255, 255);\n animation-delay: 0.44s;\n}\n\n@keyframes vjs-spinner-show {\n to {\n visibility: visible;\n }\n}\n@keyframes vjs-spinner-spin {\n 100% {\n transform: rotate(360deg);\n }\n}\n@keyframes vjs-spinner-fade {\n 0% {\n border-top-color: #73859f;\n }\n 20% {\n border-top-color: #73859f;\n }\n 35% {\n border-top-color: white;\n }\n 60% {\n border-top-color: #73859f;\n }\n 100% {\n border-top-color: #73859f;\n }\n}\n.video-js.vjs-audio-only-mode .vjs-captions-button {\n display: none;\n}\n\n.vjs-chapters-button .vjs-menu ul {\n width: 24em;\n}\n\n.video-js.vjs-audio-only-mode .vjs-descriptions-button {\n display: none;\n}\n\n.vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-svg-icon {\n width: 1.5em;\n height: 1.5em;\n}\n\n.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder {\n vertical-align: middle;\n display: inline-block;\n margin-bottom: -0.1em;\n}\n\n.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before {\n font-family: VideoJS;\n content: "\\f10c";\n font-size: 1.5em;\n line-height: inherit;\n}\n\n.video-js.vjs-audio-only-mode .vjs-subs-caps-button {\n display: none;\n}\n\n.video-js .vjs-audio-button + .vjs-menu .vjs-descriptions-menu-item .vjs-menu-item-text .vjs-icon-placeholder,\n.video-js .vjs-audio-button + .vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder {\n vertical-align: middle;\n display: inline-block;\n margin-bottom: -0.1em;\n}\n\n.video-js .vjs-audio-button + .vjs-menu .vjs-descriptions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before,\n.video-js .vjs-audio-button + .vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before {\n font-family: VideoJS;\n content: " \\f12e";\n font-size: 1.5em;\n line-height: inherit;\n}\n\n.video-js.vjs-layout-small .vjs-current-time,\n.video-js.vjs-layout-small .vjs-time-divider,\n.video-js.vjs-layout-small .vjs-duration,\n.video-js.vjs-layout-small .vjs-remaining-time,\n.video-js.vjs-layout-small .vjs-playback-rate,\n.video-js.vjs-layout-small .vjs-volume-control, .video-js.vjs-layout-x-small .vjs-current-time,\n.video-js.vjs-layout-x-small .vjs-time-divider,\n.video-js.vjs-layout-x-small .vjs-duration,\n.video-js.vjs-layout-x-small .vjs-remaining-time,\n.video-js.vjs-layout-x-small .vjs-playback-rate,\n.video-js.vjs-layout-x-small .vjs-volume-control, .video-js.vjs-layout-tiny .vjs-current-time,\n.video-js.vjs-layout-tiny .vjs-time-divider,\n.video-js.vjs-layout-tiny .vjs-duration,\n.video-js.vjs-layout-tiny .vjs-remaining-time,\n.video-js.vjs-layout-tiny .vjs-playback-rate,\n.video-js.vjs-layout-tiny .vjs-volume-control {\n display: none;\n}\n.video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover {\n width: auto;\n width: initial;\n}\n.video-js.vjs-layout-x-small .vjs-progress-control, .video-js.vjs-layout-tiny .vjs-progress-control {\n display: none;\n}\n.video-js.vjs-layout-x-small .vjs-custom-control-spacer {\n flex: auto;\n display: block;\n}\n\n.vjs-modal-dialog.vjs-text-track-settings {\n background-color: #2B333F;\n background-color: rgba(43, 51, 63, 0.75);\n color: #fff;\n height: 70%;\n}\n.vjs-spatial-navigation-enabled .vjs-modal-dialog.vjs-text-track-settings {\n height: 80%;\n}\n\n.vjs-error .vjs-text-track-settings {\n display: none;\n}\n\n.vjs-text-track-settings .vjs-modal-dialog-content {\n display: table;\n}\n\n.vjs-text-track-settings .vjs-track-settings-colors,\n.vjs-text-track-settings .vjs-track-settings-font,\n.vjs-text-track-settings .vjs-track-settings-controls {\n display: table-cell;\n}\n\n.vjs-text-track-settings .vjs-track-settings-controls {\n text-align: right;\n vertical-align: bottom;\n}\n\n@supports (display: grid) {\n .vjs-text-track-settings .vjs-modal-dialog-content {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: 1fr;\n padding: 20px 24px 0px 24px;\n }\n .vjs-track-settings-controls .vjs-default-button {\n margin-bottom: 20px;\n }\n .vjs-text-track-settings .vjs-track-settings-controls {\n grid-column: 1/-1;\n }\n .vjs-layout-small .vjs-text-track-settings .vjs-modal-dialog-content,\n .vjs-layout-x-small .vjs-text-track-settings .vjs-modal-dialog-content,\n .vjs-layout-tiny .vjs-text-track-settings .vjs-modal-dialog-content {\n grid-template-columns: 1fr;\n }\n}\n.vjs-text-track-settings select {\n font-size: inherit;\n}\n\n.vjs-track-setting > select {\n margin-right: 1em;\n margin-bottom: 0.5em;\n}\n\n.vjs-text-track-settings fieldset {\n margin: 10px;\n border: none;\n}\n\n.vjs-text-track-settings fieldset span {\n display: inline-block;\n padding: 0 0.6em 0.8em;\n}\n\n.vjs-text-track-settings fieldset span > select {\n max-width: 7.3em;\n}\n\n.vjs-text-track-settings legend {\n color: #fff;\n font-weight: bold;\n font-size: 1.2em;\n}\n\n.vjs-text-track-settings .vjs-label {\n margin: 0 0.5em 0.5em 0;\n}\n\n.vjs-track-settings-controls button:focus,\n.vjs-track-settings-controls button:active {\n outline-style: solid;\n outline-width: medium;\n background-image: linear-gradient(0deg, #fff 88%, #73859f 100%);\n}\n\n.vjs-track-settings-controls button:hover {\n color: rgba(43, 51, 63, 0.75);\n}\n\n.vjs-track-settings-controls button {\n background-color: #fff;\n background-image: linear-gradient(-180deg, #fff 88%, #73859f 100%);\n color: #2B333F;\n cursor: pointer;\n border-radius: 2px;\n}\n\n.vjs-track-settings-controls .vjs-default-button {\n margin-right: 1em;\n}\n\n.vjs-title-bar {\n background: rgba(0, 0, 0, 0.9);\n background: linear-gradient(180deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.7) 60%, rgba(0, 0, 0, 0) 100%);\n font-size: 1.2em;\n line-height: 1.5;\n transition: opacity 0.1s;\n padding: 0.666em 1.333em 4em;\n pointer-events: none;\n position: absolute;\n top: 0;\n width: 100%;\n}\n\n.vjs-error .vjs-title-bar {\n display: none;\n}\n\n.vjs-title-bar-title,\n.vjs-title-bar-description {\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.vjs-title-bar-title {\n font-weight: bold;\n margin-bottom: 0.333em;\n}\n\n.vjs-playing.vjs-user-inactive .vjs-title-bar {\n opacity: 0;\n transition: opacity 1s;\n}\n\n.video-js .vjs-skip-forward-5 {\n cursor: pointer;\n}\n.video-js .vjs-skip-forward-10 {\n cursor: pointer;\n}\n.video-js .vjs-skip-forward-30 {\n cursor: pointer;\n}\n.video-js .vjs-skip-backward-5 {\n cursor: pointer;\n}\n.video-js .vjs-skip-backward-10 {\n cursor: pointer;\n}\n.video-js .vjs-skip-backward-30 {\n cursor: pointer;\n}\n.video-js .vjs-transient-button {\n position: absolute;\n height: 3em;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(50, 50, 50, 0.5);\n cursor: pointer;\n opacity: 1;\n transition: opacity 1s;\n}\n\n.video-js:not(.vjs-has-started) .vjs-transient-button {\n display: none;\n}\n\n.video-js.not-hover .vjs-transient-button:not(.force-display),\n.video-js.vjs-user-inactive .vjs-transient-button:not(.force-display) {\n opacity: 0;\n}\n\n.video-js .vjs-transient-button span {\n padding: 0 0.5em;\n}\n\n.video-js .vjs-transient-button.vjs-left {\n left: 1em;\n}\n\n.video-js .vjs-transient-button.vjs-right {\n right: 1em;\n}\n\n.video-js .vjs-transient-button.vjs-top {\n top: 1em;\n}\n\n.video-js .vjs-transient-button.vjs-near-top {\n top: 4em;\n}\n\n.video-js .vjs-transient-button.vjs-bottom {\n bottom: 4em;\n}\n\n.video-js .vjs-transient-button:hover {\n background-color: rgba(50, 50, 50, 0.9);\n}\n\n@media print {\n .video-js > *:not(.vjs-tech):not(.vjs-poster) {\n visibility: hidden;\n }\n}\n.vjs-resize-manager {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: none;\n z-index: -1000;\n}\n\n.js-focus-visible .video-js *:focus:not(.focus-visible) {\n outline: none;\n}\n\n.video-js *:focus:not(:focus-visible) {\n outline: none;\n}\n`,""]);const p=u},6880:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,'/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/* --- Default content styles --- */\nimg,\nvideo {\n max-width: 100%;\n max-height: 500px;\n display: block;\n margin: 20px 0;\n}\n\n/* --- Form --- */\n.form-composer {\n /* Variables */\n --error-color: red;\n --form-max-width: 1280px;\n --input-bg-color: #fafafa;\n --orange-color: orange;\n\n margin: 0 auto;\n padding-top: 20px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n max-width: var(--form-max-width);\n}\n\n.form-composer .form-header {\n}\n\n.form-composer .form-header .form-name {\n font-size: 22px;\n}\n\n.form-composer .form-header .form-instruction {\n}\n\n/* --- Section --- */\n.form-composer .section {\n}\n\n.form-composer .section .section-header:not(.collapsable):hover {\n cursor: initial;\n opacity: 1;\n}\n\n.form-composer .section .section-header.collapsable:hover {\n cursor: pointer;\n opacity: 0.8;\n}\n\n.form-composer .section .section-header.has-invalid-fields .section-name {\n color: var(--error-color);\n}\n\n.form-composer .section .section-name {\n font-size: 18px;\n}\n\n.form-composer .section .section-instruction {\n}\n\n/* --- Fieldset --- */\n.form-composer .section .fieldset {\n margin-bottom: 20px;\n}\n\n.form-composer .section .fieldset .fieldset-header {\n background-color: #d9e0df;\n}\n\n.form-composer .section .fieldset .fieldset-name {\n margin: 0;\n}\n\n.form-composer .section .fieldset .fieldset-instruction {\n}\n\n/* --- Row --- */\n.form-composer .section .fieldset .row {\n}\n\n.form-composer .section .fieldset .row:not(:last-child) {\n margin-bottom: 20px;\n}\n\n.form-composer .section .fieldset .row .row-instruction {\n margin-bottom: 10px;\n font-size: 15px;\n}\n\n.form-composer .section .fieldset .row .row-help {\n padding-top: 5px;\n padding-left: 0;\n padding-right: 0;\n margin-left: 15px;\n margin-right: 15px;\n font-size: 12px;\n font-style: italic;\n color: grey;\n border-top: 1px solid #ccc;\n}\n\n/* --- Field --- */\n.form-composer .section .fieldset .field {\n margin-bottom: 10px;\n}\n\n.form-composer .section .fieldset .field .field-help {\n font-size: 10px;\n}\n\n.form-composer .section .fieldset .field {\n}\n\n.form-composer .section .fieldset .field label {\n width: 100%;\n}\n\n.form-composer .section .fieldset .field.required label:after {\n content: "*";\n color: var(--error-color);\n margin-left: 3px;\n}\n\n.form-composer .form-buttons-separator {\n margin-bottom: 40px;\n width: 70%;\n border: 1px solid black;\n opacity: 0.2;\n}\n\n/* --- Buttons --- */\n.form-composer .form-buttons {\n margin-bottom: 40px;\n display: flex;\n justify-content: center;\n}\n\n.form-composer .form-buttons .button-submit {\n}\n\n.form-composer .form-instruction-dialog {\n max-width: var(--form-max-width);\n}\n\n/* --- Bootstrap overriding --- */\n\n.form-control::placeholder {\n color: #c7c7c7 !important;\n}\n.form-control::-moz-placeholder {\n color: #c7c7c7 !important;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #c7c7c7 !important;\n}\n.form-control::-webkit-input-placeholder {\n color: #c7c7c7 !important;\n}\n\n.non-collapsable {\n display: initial !important;\n}\n\n/* --- Custom classes for users --- */\n\n.centered {\n display: block;\n text-align: center;\n}\n\n.hidden,\n.hidden-type {\n display: none;\n visibility: hidden;\n}\n',""]);const s=a},1147:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.fc-checkbox-field .form-check.disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.fc-checkbox-field .form-check.checkbox {\n display: flex;\n align-items: center;\n}\n\n.fc-checkbox-field .form-check.checkbox .form-check-input {\n margin-top: 0;\n width: 20px;\n height: 20px;\n border: 2px solid #ced4da;\n background-color: transparent;\n}\n\n.fc-checkbox-field .form-check.checkbox .form-check-input {\n border-radius: 4px;\n}\n\n.fc-checkbox-field .form-check.checkbox .form-check-label {\n margin-left: 10px;\n}\n\n.fc-checkbox-field .form-check.checkbox .form-check-input.checked {\n background-color: #495057;\n}\n\n.fc-checkbox-field .form-check.checkbox.is-invalid .form-check-input {\n border-color: var(--error-color);\n}\n\n.fc-checkbox-field .form-check:not(.disabled):hover .form-check-input {\n border-color: #495057;\n cursor: pointer;\n}\n\n.fc-checkbox-field .form-check:not(.disabled):hover .form-check-label {\n opacity: 0.8;\n cursor: pointer;\n}\n",""]);const s=a},4528:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.fc-file-field {\n position: relative;\n}\n\n.fc-file-field .review-file-button {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 4;\n display: block;\n width: 88px;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n background-color: var(--orange-color);\n color: white;\n border: 1px solid var(--orange-color);\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n cursor: pointer;\n}\n\n.fc-file-field .review-file-button:hover {\n background-color: #de8800;\n border-color: #de8800;\n}\n\n.fc-file-field .file-preview {\n display: flex;\n max-width: 400px;\n max-height: 400px;\n}\n\n.fc-file-field .file-preview img {\n max-width: 400px;\n max-height: 400px;\n}\n\n.fc-file-field .file-preview .audio-wrapper {\n width: 100%;\n padding: 25px;\n}\n\n.fc-file-field .file-preview .audio-wrapper audio {\n width: 100%;\n}\n\n.fc-file-field .file-preview .pdf-wrapper iframe {\n margin: 20px 0;\n}\n\n.fc-input-field input {\n width: 100%;\n}\n\n.fc-input-field input {\n background-color: var(--input-bg-color);\n}\n",""]);const s=a},1392:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.fc-input-field {\n width: 100%;\n}\n\n.fc-input-field {\n background-color: var(--input-bg-color);\n}\n",""]);const s=a},5927:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.fc-radio-field .form-check.disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.fc-radio-field .form-check.radio .form-check-input {\n margin-top: 0;\n width: 20px;\n height: 20px;\n border: 2px solid #ced4da;\n background-color: transparent;\n}\n\n.fc-radio-field .form-check.radio .form-check-input {\n border-radius: 50%;\n}\n\n.fc-radio-field .form-check.radio .form-check-label {\n margin-left: 10px;\n}\n\n.fc-radio-field .form-check.radio .form-check-input.checked {\n background-color: #495057;\n}\n\n.fc-radio-field .form-check.radio.is-invalid .form-check-input {\n border-color: var(--error-color);\n}\n\n.fc-radio-field .form-check:not(.disabled):hover .form-check-input {\n border-color: #495057;\n cursor: pointer;\n}\n\n.fc-radio-field .form-check:not(.disabled):hover .form-check-label {\n opacity: 0.8;\n cursor: pointer;\n}\n",""]);const s=a},5006:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.fc-select-field {\n width: 100%;\n}\n\n.fc-select-field {\n background-color: var(--input-bg-color);\n}\n",""]);const s=a},5264:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.fc-textarea-field {\n width: 100%;\n}\n\n.fc-textarea-field {\n background-color: var(--input-bg-color);\n}\n",""]);const s=a},9775:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.task-instruction-button {\n position: fixed;\n right: 10px;\n top: 10px;\n}\n",""]);const s=a},696:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.task-instruction-modal {\n padding: 10px 0;\n background-color: #ffffff;\n}\n\n.task-instruction-modal .modal-dialog {\n width: initial;\n max-height: 100%;\n margin: 0 auto;\n}\n\n.task-instruction-modal .modal-dialog .modal-content {\n box-shadow: 0 10px 20px 10px rgba(0, 0, 0, 0.5);\n -webkit-box-shadow: 0 10px 20px 10px rgba(0, 0, 0, 0.5);\n}\n\n.task-instruction-modal .modal-dialog .modal-content .modal-header {\n padding: 10px 20px;\n align-items: center;\n background-color: #cce5ff;\n}\n\n.task-instruction-modal\n .modal-dialog\n .modal-content\n .modal-header\n .modal-title {\n font-size: 21px;\n font-weight: 500;\n line-height: initial;\n}\n\n.task-instruction-modal .modal-dialog .modal-content .modal-header .close {\n margin: 0 0 0 auto;\n font-size: 30px;\n width: 40px;\n height: 40px;\n line-height: 0;\n}\n",""]);const s=a},7237:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.annotation-track {\n position: relative;\n width: 100%;\n min-height: 40px;\n display: flex;\n flex-direction: column;\n background-color: var(--track-bg-color-inactive);\n}\n\n.annotation-track:not(.non-clickable):not(.active):hover {\n background-color: var(--track-bg-color-inactive-hover);\n cursor: pointer;\n}\n\n.annotation-track.active {\n background-color: var(--track-bg-color-active);\n}\n\n.annotation-track .track-name-small {\n position: absolute;\n top: 2px;\n left: 4px;\n font-size: 10px;\n color: #ffffff;\n}\n\n.annotation-track .segments-count {\n position: absolute;\n top: 2px;\n right: 4px;\n font-size: 10px;\n font-style: italic;\n color: #ffffff;\n}\n\n.annotation-track .track-info {\n position: relative;\n height: 40px;\n display: flex;\n flex-direction: row;\n gap: 4px;\n align-items: center;\n padding: 0 190px 0 10px;\n}\n\n.annotation-track .track-info .track-name-label {\n font-size: 14px;\n margin-right: 6px;\n}\n\n.annotation-track .track-info .track-name {\n font-size: 14px;\n font-weight: bold;\n}\n\n.annotation-track .track-info .buttons {\n display: flex;\n flex-direction: row;\n gap: 6px;\n margin-left: 6px;\n}\n\n.annotation-track .track-info .buttons .btn {\n}\n\n.annotation-track .track-info .track-buttons {\n position: absolute;\n right: 10px;\n display: none;\n gap: 4px;\n}\n\n.annotation-track.active .track-info .track-buttons {\n display: flex;\n}\n\n.annotation-track .segments {\n position: relative;\n width: 100%;\n height: 50px;\n display: flex;\n flex-direction: row;\n gap: 4px;\n align-items: center;\n padding-left: var(--segments-padding-left);\n padding-right: var(--segments-padding-right);\n}\n\n.annotation-track .segments .progress-bar {\n width: 100%;\n height: var(--segment-height-inactive);\n background-color: #000000;\n border-radius: 4px;\n opacity: 0.2;\n}\n\n.annotation-track .overlapping-segments.is-invalid {\n display: flex;\n justify-content: center;\n}\n\n.annotation-track .segment-info {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px;\n}\n\n.annotation-track .segment-info .time {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 6px;\n}\n\n.annotation-track .segment-info .time span {\n font-size: 14px;\n}\n\n.annotation-track .segment-info .time input {\n width: 60px;\n}\n\n.annotation-track .segment-info textarea {\n resize: vertical;\n min-height: 45px;\n max-height: 200px;\n}\n\n.annotation-track .segment-info .field-label {\n font-size: 12px;\n}\n\n.annotation-track .segment-info .field-help {\n font-size: 10px;\n}\n\n.annotation-track .segment-info .fc-radio-field .form-check-label,\n.annotation-track .segment-info .fc-checkbox-field .form-check-label,\n.annotation-track\n .segment-info\n .fc-select-field\n .filter-option\n .filter-option-inner-inner,\n.annotation-track\n .segment-info\n .fc-select-field\n .dropdown-menu\n .dropdown-item\n .text {\n font-size: 0.875rem; /* Same as in inputs */\n}\n\n.annotation-track .segment-info .segment-buttons {\n position: absolute;\n top: 10px;\n right: 10px;\n display: flex;\n gap: 4px;\n}\n\n.annotation-track .segment-info.is-invalid .invalid-feedback {\n display: block;\n}\n",""]);const s=a},2434:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.video-annotation .annotation-tracks {\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: 1px;\n margin-bottom: 60px;\n}\n\n.video-annotation .annotation-tracks .tracks-buttons {\n width: 100%;\n display: flex;\n flex-direction: row;\n justify-content: center;\n margin-bottom: 10px;\n}\n",""]);const s=a},2709:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.segment {\n position: absolute;\n width: 40px;\n height: var(--segment-height-inactive);\n background-color: #ffffff;\n border-radius: 4px;\n border: 1px solid #dddddd;\n box-sizing: border-box;\n opacity: 0.8;\n}\n\n.segment:not(.non-clickable):hover {\n height: var(--segment-height-active);\n opacity: 1;\n cursor: pointer;\n}\n\n.segment.active {\n height: var(--segment-height-active);\n opacity: 1;\n}\n",""]);const s=a},9422:(e,t,n)=>{"use strict";n.d(t,{A:()=>s});var r=n(1601),i=n.n(r),o=n(6314),a=n.n(o)()(i());a.push([e.id,"/*\n * Copyright (c) Meta Platforms and its affiliates.\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.video-annotation {\n --annotator-width: 700px;\n\n --track-bg-color-active: #ecdadf;\n --track-bg-color-inactive: #666666;\n --track-bg-color-inactive-hover: #777777;\n\n --segments-padding-left: initial;\n --segments-padding-right: initial;\n\n --segment-height-active: 22px;\n --segment-height-inactive: 12px;\n\n --blue-color: #439acb;\n --brown-color: #9f4f33;\n --green-color: #64b943;\n --orange-color: #f18800;\n --purple-color: #9e5aba;\n --red-color: #da4a4a;\n --yellow-color: #eadb37;\n\n --input-bg-color: #fafafa;\n\n width: var(--annotator-width);\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.video-annotation hr {\n width: 100%;\n border-top: 1px solid rgba(0, 0, 0, 0.2);\n}\n\n.video-annotation .title {\n margin-bottom: 20px;\n}\n\n.video-annotation .instruction {\n margin-bottom: 20px;\n}\n\n.video-annotation .instruction-hint {\n margin-bottom: 20px;\n}\n\n.video-annotation .video-player-container {\n margin-bottom: 20px;\n}\n\n.video-annotation .annotator-buttons-separator {\n margin-bottom: 40px;\n width: 70%;\n border: 1px solid black;\n opacity: 0.2;\n}\n\n.video-annotation .annotator-buttons {\n margin-bottom: 40px;\n display: flex;\n justify-content: center;\n}\n\n.video-annotation .annotator-instruction-dialog {\n max-width: var(--annotator-width);\n}\n\n/* --- VideoJS --- */\n.vjs-control-bar {\n display: flex !important;\n}\n\n.vjs-tech {\n margin: initial !important;\n max-height: initial !important;\n}\n\n/* --- Floating labels --- */\n:root {\n --input-padding-x: 0.75rem;\n --input-padding-y: 0.65rem;\n --label-color: #777777;\n}\n\n.form-label-group.floating-label {\n position: relative;\n}\n\n.form-label-group.floating-label > input,\n.form-label-group.floating-label > textarea,\n.form-label-group.floating-label > label {\n padding: var(--input-padding-y) var(--input-padding-x);\n min-height: 45px;\n}\n\n.form-label-group.floating-label > label {\n position: absolute;\n top: 0;\n left: 0;\n display: block;\n width: 100%;\n margin-bottom: 0; /* Override default `